@payloadcms/figma 0.0.1-alpha.74 → 0.0.1-alpha.75
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/figma-api.d.ts +2 -1
- package/dist/api/figma-api.js +33 -3
- package/dist/auth/jwt-validator.d.ts +12 -2
- package/dist/auth/jwt-validator.js +19 -1
- package/dist/auth/project-token.js +3 -2
- package/dist/auth/types.d.ts +5 -1
- package/dist/auth/types.js +1 -1
- package/dist/cli.js +7 -0
- package/dist/commands/upload-schema.d.ts +8 -0
- package/dist/commands/upload-schema.js +91 -0
- package/dist/db-content-api/generated/content-api-types.d.ts +4188 -591
- package/dist/db-content-api/utilities/data/pointShape.d.ts +3 -0
- package/dist/db-content-api/utilities/data/pointShape.js +43 -0
- package/dist/db-content-api/utilities/data/richTextShape.d.ts +3 -0
- package/dist/db-content-api/utilities/data/richTextShape.js +56 -0
- package/dist/db-content-api/utilities/schema/buildDocumentSchema.d.ts +56 -0
- package/dist/db-content-api/utilities/schema/buildDocumentSchema.js +183 -0
- package/dist/oauth/defaults.js +8 -0
- package/dist/oauth/endpoints/getLoginEndpoint.js +4 -6
- package/dist/oauth/strategy/index.js +11 -19
- package/dist/oauth/utilities/establishSession.d.ts +4 -4
- package/dist/oauth/utilities/establishSession.js +9 -10
- package/dist/oauth/utilities/refreshTokens.js +13 -15
- package/dist/plugin/build-config.d.ts +1 -0
- package/dist/plugin/build-config.js +66 -1
- package/dist/utils/load-payload-config.d.ts +9 -0
- package/dist/utils/load-payload-config.js +27 -0
- package/dist/utils/messages.js +7 -0
- package/package.json +1 -1
package/dist/api/figma-api.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export declare class FigmaApiError extends Error {
|
|
|
30
30
|
* requests to the Content API. The token contains the CMS ID and
|
|
31
31
|
* user identifying information in its claims.
|
|
32
32
|
*
|
|
33
|
-
* Endpoint:
|
|
33
|
+
* Endpoint: POST https://api.figma.com/v1/cms/content-systems/:id/token/
|
|
34
34
|
*
|
|
35
35
|
* @param accessToken - OAuth access token for authentication
|
|
36
36
|
* @param tenantId - The tenant/CMS ID
|
|
@@ -38,6 +38,7 @@ export declare class FigmaApiError extends Error {
|
|
|
38
38
|
* @throws {FigmaApiError} If the API call fails
|
|
39
39
|
*/
|
|
40
40
|
export declare function getProjectToken(credential: AuthCredential, tenantId: string): Promise<ProjectToken>;
|
|
41
|
+
export declare function getPayloadAdminToken(credential: AuthCredential, environmentId: string): Promise<ProjectToken>;
|
|
41
42
|
/**
|
|
42
43
|
* Get user info from Figma API
|
|
43
44
|
*
|
package/dist/api/figma-api.js
CHANGED
|
@@ -24,7 +24,7 @@ import { parseControlPlaneApiError } from './error-response.js';
|
|
|
24
24
|
* requests to the Content API. The token contains the CMS ID and
|
|
25
25
|
* user identifying information in its claims.
|
|
26
26
|
*
|
|
27
|
-
* Endpoint:
|
|
27
|
+
* Endpoint: POST https://api.figma.com/v1/cms/content-systems/:id/token/
|
|
28
28
|
*
|
|
29
29
|
* @param accessToken - OAuth access token for authentication
|
|
30
30
|
* @param tenantId - The tenant/CMS ID
|
|
@@ -66,7 +66,7 @@ import { parseControlPlaneApiError } from './error-response.js';
|
|
|
66
66
|
if (shouldMock) {
|
|
67
67
|
// MOCK IMPLEMENTATION: Generate a mock JWT token for testing
|
|
68
68
|
log.debug('Using mock project token API for getProjectToken (FIGMA_MOCK_PROJECT_TOKEN = "true")');
|
|
69
|
-
const mockToken = createMockJWT(tenantId
|
|
69
|
+
const mockToken = createMockJWT(`csid_${tenantId}`, 25) // 25 minutes expiry
|
|
70
70
|
;
|
|
71
71
|
// Mock response data
|
|
72
72
|
const data = {
|
|
@@ -87,7 +87,7 @@ import { parseControlPlaneApiError } from './error-response.js';
|
|
|
87
87
|
}
|
|
88
88
|
// REAL API IMPLEMENTATION
|
|
89
89
|
const baseUrl = getEnvConfig().apiBaseUrl;
|
|
90
|
-
const url = `${baseUrl}/v1/cms/${tenantId}/token/`;
|
|
90
|
+
const url = `${baseUrl}/v1/cms/content-systems/${tenantId}/token/`;
|
|
91
91
|
const response = await fetch(url, {
|
|
92
92
|
headers: getAuthHeaders(credential),
|
|
93
93
|
method: 'POST'
|
|
@@ -122,6 +122,36 @@ import { parseControlPlaneApiError } from './error-response.js';
|
|
|
122
122
|
throw new FigmaApiError(`Failed to get project token: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, error instanceof Error ? error : undefined);
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
+
export async function getPayloadAdminToken(credential, environmentId) {
|
|
126
|
+
if (process.env.FIGMA_MOCK_PROJECT_TOKEN === 'true') {
|
|
127
|
+
const token = createMockJWT(`eid_${environmentId}`, 25);
|
|
128
|
+
return {
|
|
129
|
+
expiresAt: parseJwtExpiry(token),
|
|
130
|
+
tenantId: environmentId,
|
|
131
|
+
token
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const baseUrl = getEnvConfig().apiBaseUrl;
|
|
135
|
+
const response = await fetch(`${baseUrl}/v1/cms/environments/${environmentId}/admin-token`, {
|
|
136
|
+
headers: getAuthHeaders(credential),
|
|
137
|
+
method: 'POST'
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
throw new FigmaApiError(await parseControlPlaneApiError({
|
|
141
|
+
context: 'get Payload Admin token',
|
|
142
|
+
response
|
|
143
|
+
}), response.status);
|
|
144
|
+
}
|
|
145
|
+
const data = await response.json();
|
|
146
|
+
if (!data.meta?.token) {
|
|
147
|
+
throw new FigmaApiError('Invalid response: missing token in meta');
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
expiresAt: parseJwtExpiry(data.meta.token),
|
|
151
|
+
tenantId: environmentId,
|
|
152
|
+
token: data.meta.token
|
|
153
|
+
};
|
|
154
|
+
}
|
|
125
155
|
/**
|
|
126
156
|
* Create a mock JWT token for testing
|
|
127
157
|
* TODO: Remove this when real API is available
|
|
@@ -4,9 +4,9 @@ import type { JWTPayload } from './types.js';
|
|
|
4
4
|
* JWT validation error with specific error codes
|
|
5
5
|
*/
|
|
6
6
|
export declare class JWTValidationError extends Error {
|
|
7
|
-
code: 'AUDIENCE_INVALID' | 'EXPIRED' | 'JWKS_FETCH_FAILED' | 'MISSING_KID' | 'SIGNATURE_INVALID';
|
|
7
|
+
code: 'AUDIENCE_INVALID' | 'EXPIRED' | 'JWKS_FETCH_FAILED' | 'MISSING_KID' | 'SIGNATURE_INVALID' | 'TOKEN_CLAIMS_INVALID';
|
|
8
8
|
cause?: Error | undefined;
|
|
9
|
-
constructor(message: string, code: 'AUDIENCE_INVALID' | 'EXPIRED' | 'JWKS_FETCH_FAILED' | 'MISSING_KID' | 'SIGNATURE_INVALID', cause?: Error | undefined);
|
|
9
|
+
constructor(message: string, code: 'AUDIENCE_INVALID' | 'EXPIRED' | 'JWKS_FETCH_FAILED' | 'MISSING_KID' | 'SIGNATURE_INVALID' | 'TOKEN_CLAIMS_INVALID', cause?: Error | undefined);
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Validate a project token JWT
|
|
@@ -36,6 +36,16 @@ export declare function validateProjectToken(params: {
|
|
|
36
36
|
environment: Environment;
|
|
37
37
|
token: string;
|
|
38
38
|
}): Promise<JWTPayload>;
|
|
39
|
+
export declare function validatePayloadAdminToken(params: {
|
|
40
|
+
audience: string;
|
|
41
|
+
environment: Environment;
|
|
42
|
+
token: string;
|
|
43
|
+
}): Promise<JWTPayload>;
|
|
44
|
+
export declare function validateContentApiToken(params: {
|
|
45
|
+
audience: string;
|
|
46
|
+
environment: Environment;
|
|
47
|
+
token: string;
|
|
48
|
+
}): Promise<JWTPayload>;
|
|
39
49
|
/**
|
|
40
50
|
* Clear JWKS cache (useful for testing or forcing refresh)
|
|
41
51
|
*
|
|
@@ -63,7 +63,7 @@ import { getEnvConfig } from '../constants.js';
|
|
|
63
63
|
try {
|
|
64
64
|
const JWKS = getJWKSResolver(environment);
|
|
65
65
|
const { payload } = await jwtVerify(token, JWKS, {
|
|
66
|
-
clockTolerance:
|
|
66
|
+
clockTolerance: 30,
|
|
67
67
|
...audience ? {
|
|
68
68
|
audience
|
|
69
69
|
} : {}
|
|
@@ -108,6 +108,24 @@ import { getEnvConfig } from '../constants.js';
|
|
|
108
108
|
throw new JWTValidationError(`JWT validation failed: ${message}`, 'SIGNATURE_INVALID', error instanceof Error ? error : undefined);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
export async function validatePayloadAdminToken(params) {
|
|
112
|
+
const payload = await validateProjectToken({
|
|
113
|
+
...params,
|
|
114
|
+
audience: `eid_${params.audience}`
|
|
115
|
+
});
|
|
116
|
+
if (typeof payload.sub !== 'string' || typeof payload.email !== 'string') {
|
|
117
|
+
throw new JWTValidationError('Payload Admin token claims are invalid', 'TOKEN_CLAIMS_INVALID');
|
|
118
|
+
}
|
|
119
|
+
return payload;
|
|
120
|
+
}
|
|
121
|
+
export async function validateContentApiToken(params) {
|
|
122
|
+
// Content API tokens are issued by the content-system-scoped endpoint, which
|
|
123
|
+
// prefixes the `aud` with `csid_` (mirrors the `eid_` admin-token scoping).
|
|
124
|
+
return validateProjectToken({
|
|
125
|
+
...params,
|
|
126
|
+
audience: `csid_${params.audience}`
|
|
127
|
+
});
|
|
128
|
+
}
|
|
111
129
|
/**
|
|
112
130
|
* Clear JWKS cache (useful for testing or forcing refresh)
|
|
113
131
|
*
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { PROJECT_TOKEN_EXPIRY_BUFFER_SECONDS } from '../config/oauth.js';
|
|
9
9
|
import { getInfraEnvironment } from '../constants.js';
|
|
10
10
|
import * as log from '../utils/log.js';
|
|
11
|
-
import { JWTValidationError,
|
|
11
|
+
import { JWTValidationError, validateContentApiToken } from './jwt-validator.js';
|
|
12
12
|
import { getValidCredential } from './oauth-flow.js';
|
|
13
13
|
/**
|
|
14
14
|
* Error during project token operations
|
|
@@ -153,7 +153,8 @@ function isCachedTokenValid(entry) {
|
|
|
153
153
|
validatedClaims = parseJWTClaims(fetchedToken.token);
|
|
154
154
|
} else {
|
|
155
155
|
try {
|
|
156
|
-
validatedClaims = await
|
|
156
|
+
validatedClaims = await validateContentApiToken({
|
|
157
|
+
audience: resolvedTenantId,
|
|
157
158
|
environment: getInfraEnvironment(),
|
|
158
159
|
token: fetchedToken.token
|
|
159
160
|
});
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -197,8 +197,12 @@ export interface JWTPayload extends JoseJWTPayload {
|
|
|
197
197
|
[key: string]: unknown;
|
|
198
198
|
/** User email address */
|
|
199
199
|
email?: string;
|
|
200
|
+
/** Make permission tier the authed user holds on this CMS */
|
|
201
|
+
make_permission?: 'edit' | 'view';
|
|
200
202
|
/** Tenant/CMS ID this token is scoped to */
|
|
201
203
|
tenant_id?: string;
|
|
204
|
+
/** File user groups the authed user is a member of (scoped to the linked Make file) */
|
|
205
|
+
user_groups?: string[];
|
|
202
206
|
}
|
|
203
207
|
/**
|
|
204
208
|
* Project token (JWT) for authenticating to Content API
|
|
@@ -216,7 +220,7 @@ export interface ProjectToken {
|
|
|
216
220
|
}
|
|
217
221
|
/**
|
|
218
222
|
* Raw project token response from Figma API
|
|
219
|
-
* Response from
|
|
223
|
+
* Response from POST https://api.figma.com/v1/cms/content-systems/:id/token/
|
|
220
224
|
*/
|
|
221
225
|
export interface ProjectTokenResponse {
|
|
222
226
|
/** Response metadata containing the JWT token */
|
package/dist/auth/types.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { listTokensCommand } from './commands/list-tokens.js';
|
|
|
12
12
|
import { loginCommand } from './commands/login.js';
|
|
13
13
|
import { logoutCommand } from './commands/logout.js';
|
|
14
14
|
import { upgradeCommand } from './commands/upgrade.js';
|
|
15
|
+
import { uploadSchemaCommand } from './commands/upload-schema.js';
|
|
15
16
|
import { setInfraEnvironment } from './constants.js';
|
|
16
17
|
import { helpMessage } from './utils/messages.js';
|
|
17
18
|
/**
|
|
@@ -178,6 +179,12 @@ class Main {
|
|
|
178
179
|
isDryRun: this.args['--dry-run']
|
|
179
180
|
});
|
|
180
181
|
break;
|
|
182
|
+
case 'upload-schema':
|
|
183
|
+
await uploadSchemaCommand({
|
|
184
|
+
id: this.args['--id'],
|
|
185
|
+
env: this.args['--env']
|
|
186
|
+
});
|
|
187
|
+
break;
|
|
181
188
|
default:
|
|
182
189
|
p.log.error(pc.red(`Unknown command: ${subcommand}`));
|
|
183
190
|
p.note('Use --help to see available commands', 'Tip');
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface UploadSchemaCommandOptions {
|
|
2
|
+
/** Environment name (overrides FIGMA_ENVIRONMENT_NAME). */
|
|
3
|
+
env?: string;
|
|
4
|
+
/** CMS Resource ID (overrides FIGMA_PROJECT_ID). */
|
|
5
|
+
id?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Upload the current Payload collection schema for Content API migration tooling. */
|
|
8
|
+
export declare function uploadSchemaCommand(options?: UploadSchemaCommandOptions): Promise<void>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { getBootstrapInfo } from '../api/control-plane.js';
|
|
4
|
+
import { tryGetCredential } from '../auth/oauth-flow.js';
|
|
5
|
+
import { getValidProjectToken } from '../auth/project-token.js';
|
|
6
|
+
import { getTokenStore } from '../auth/token-store.js';
|
|
7
|
+
import { getEnvConfig } from '../constants.js';
|
|
8
|
+
import { buildDocumentSchema } from '../db-content-api/utilities/schema/buildDocumentSchema.js';
|
|
9
|
+
import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
|
|
10
|
+
import { getEnvVar } from '../utils/env-management.js';
|
|
11
|
+
import { loadPayloadConfig } from '../utils/load-payload-config.js';
|
|
12
|
+
import { resolveEnvironment } from '../utils/resolve-environment.js';
|
|
13
|
+
import { loginCommand } from './login.js';
|
|
14
|
+
/** Upload the current Payload collection schema for Content API migration tooling. */ export async function uploadSchemaCommand(options = {}) {
|
|
15
|
+
const projectPath = process.cwd();
|
|
16
|
+
const projectId = options.id ?? await getEnvVar(projectPath, 'FIGMA_PROJECT_ID') ?? undefined;
|
|
17
|
+
const environmentName = options.env ?? await getEnvVar(projectPath, 'FIGMA_ENVIRONMENT_NAME') ?? undefined;
|
|
18
|
+
if (!projectId) {
|
|
19
|
+
p.log.error(pc.red('Project ID not found. Run `@payloadcms/figma init` first, or use --id.'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
// Bootstrap still requires a CLI login when the upload uses the Content API key.
|
|
23
|
+
const tokenStore = getTokenStore();
|
|
24
|
+
let credential = await tryGetCredential(tokenStore);
|
|
25
|
+
if (!credential) {
|
|
26
|
+
p.log.message('Please log in to continue');
|
|
27
|
+
await loginCommand();
|
|
28
|
+
credential = await tryGetCredential(tokenStore);
|
|
29
|
+
}
|
|
30
|
+
if (!credential) {
|
|
31
|
+
p.log.error(pc.red('Authentication failed'));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const spinner = p.spinner();
|
|
35
|
+
spinner.start('Resolving schema upload target...');
|
|
36
|
+
try {
|
|
37
|
+
const bootstrapInfo = await getBootstrapInfo(credential, projectId);
|
|
38
|
+
const environment = resolveEnvironment({
|
|
39
|
+
environmentName,
|
|
40
|
+
environments: bootstrapInfo.environments
|
|
41
|
+
});
|
|
42
|
+
cacheAllEnvironments(tokenStore, projectId, bootstrapInfo);
|
|
43
|
+
spinner.message('Loading Payload config...');
|
|
44
|
+
// --id/--env select the upload target only. Collections conditionally configured from
|
|
45
|
+
// FIGMA_ENVIRONMENT_NAME still use process.env/.env and may not match that upload target.
|
|
46
|
+
const config = await loadPayloadConfig(projectPath);
|
|
47
|
+
const documentSchema = buildDocumentSchema(config);
|
|
48
|
+
spinner.message('Uploading collection schema...');
|
|
49
|
+
const apiKey = process.env.FIGMA_CONTENT_API_ACCESS_KEY;
|
|
50
|
+
const projectToken = apiKey ? null : await getValidProjectToken({
|
|
51
|
+
projectInfo: {
|
|
52
|
+
environmentName: environment.name,
|
|
53
|
+
projectId
|
|
54
|
+
},
|
|
55
|
+
tenantId: environment.contentSystemId,
|
|
56
|
+
tokenStore
|
|
57
|
+
});
|
|
58
|
+
if (!apiKey && !projectToken) {
|
|
59
|
+
throw new Error('Could not get a project token. Run `@payloadcms/figma login` and retry.');
|
|
60
|
+
}
|
|
61
|
+
// --infra-env is applied by the CLI before this fallback selects the Content API URL.
|
|
62
|
+
const contentApiUrl = process.env.FIGMA_CONTENT_API_URL || getEnvConfig().contentApiUrl;
|
|
63
|
+
if (!contentApiUrl) {
|
|
64
|
+
throw new Error('FIGMA_CONTENT_API_URL is required for this infrastructure environment.');
|
|
65
|
+
}
|
|
66
|
+
const response = await fetch(`${contentApiUrl}/api/v0/content_systems/${encodeURIComponent(environment.contentSystemId)}/jsonb_to_index_migration_schema`, {
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
documentSchema
|
|
69
|
+
}),
|
|
70
|
+
headers: {
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
...apiKey ? {
|
|
73
|
+
'X-Api-Key': apiKey
|
|
74
|
+
} : {
|
|
75
|
+
Authorization: `Bearer ${projectToken}`
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
method: 'PUT'
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const responseBody = await response.text();
|
|
82
|
+
throw new Error(`Content API returned ${response.status}${responseBody ? `: ${responseBody}` : ''}`);
|
|
83
|
+
}
|
|
84
|
+
const collectionCount = Object.keys(documentSchema.collections).length;
|
|
85
|
+
spinner.stop(pc.green(`Uploaded ${collectionCount} collection schema${collectionCount === 1 ? '' : 's'} to ${environment.name}`));
|
|
86
|
+
} catch (error) {
|
|
87
|
+
spinner.stop(pc.red('Failed to upload collection schema'));
|
|
88
|
+
p.log.error(error instanceof Error ? error.message : 'Unknown error');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|