@payloadcms/figma 0.1.0-alpha.3 → 0.1.0-alpha.5
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/check-project-subdomain-availability.d.ts +12 -0
- package/dist/api/check-project-subdomain-availability.js +32 -0
- package/dist/api/control-plane-error.d.ts +6 -0
- package/dist/api/control-plane-error.js +8 -0
- package/dist/api/control-plane-fetch.d.ts +7 -0
- package/dist/api/control-plane-fetch.js +37 -0
- package/dist/api/control-plane.d.ts +0 -8
- package/dist/api/control-plane.js +1 -47
- package/dist/api/create-project.d.ts +18 -0
- package/dist/api/create-project.js +37 -0
- package/dist/api/list-project-workspaces.d.ts +8 -0
- package/dist/api/list-project-workspaces.js +28 -0
- package/dist/api/project-resource-type.d.ts +1 -0
- package/dist/api/project-resource-type.js +1 -0
- package/dist/auth/headless-oauth-flow.d.ts +23 -0
- package/dist/auth/headless-oauth-flow.js +69 -0
- package/dist/auth/oauth-flow.d.ts +2 -0
- package/dist/auth/oauth-flow.js +13 -8
- package/dist/auth/token-store.d.ts +4 -1
- package/dist/auth/token-store.js +14 -0
- package/dist/auth/types.d.ts +12 -0
- package/dist/cli.d.ts +10 -1
- package/dist/cli.js +39 -4
- package/dist/commands/bootstrap.js +2 -1
- package/dist/commands/deploy.d.ts +10 -0
- package/dist/commands/deploy.js +39 -11
- package/dist/commands/detect-project-resource-type.d.ts +3 -0
- package/dist/commands/detect-project-resource-type.js +28 -0
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +163 -78
- package/dist/commands/login.d.ts +11 -1
- package/dist/commands/login.js +66 -4
- package/dist/commands/prompt-to-create-project/format-project-subdomain.d.ts +1 -0
- package/dist/commands/prompt-to-create-project/format-project-subdomain.js +3 -0
- package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.d.ts +1 -0
- package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.js +7 -0
- package/dist/commands/prompt-to-create-project/get-default-project-name.d.ts +1 -0
- package/dist/commands/prompt-to-create-project/get-default-project-name.js +13 -0
- package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.d.ts +7 -0
- package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.js +32 -0
- package/dist/commands/prompt-to-create-project/resolve-project-region.d.ts +8 -0
- package/dist/commands/prompt-to-create-project/resolve-project-region.js +27 -0
- package/dist/commands/prompt-to-create-project/resolve-project-subdomain.d.ts +9 -0
- package/dist/commands/prompt-to-create-project/resolve-project-subdomain.js +54 -0
- package/dist/commands/prompt-to-create-project/resolve-text-input.d.ts +9 -0
- package/dist/commands/prompt-to-create-project/resolve-text-input.js +23 -0
- package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.d.ts +12 -0
- package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.js +36 -0
- package/dist/commands/prompt-to-create-project/validate-subdomain.d.ts +1 -0
- package/dist/commands/prompt-to-create-project/validate-subdomain.js +9 -0
- package/dist/commands/prompt-to-create-project.d.ts +24 -0
- package/dist/commands/prompt-to-create-project.js +69 -0
- package/dist/commands/setup-agent.d.ts +12 -0
- package/dist/commands/setup-agent.js +123 -0
- package/dist/commands/write-project-configuration.d.ts +12 -0
- package/dist/commands/write-project-configuration.js +16 -0
- package/dist/db-content-api/index.js +52 -33
- package/dist/db-content-api/utilities/data/injectGlobalType.d.ts +6 -0
- package/dist/db-content-api/utilities/data/injectGlobalType.js +11 -0
- package/dist/db-content-api/utilities/isRecord.d.ts +9 -0
- package/dist/db-content-api/utilities/isRecord.js +19 -0
- package/dist/db-content-api/utilities/joins.js +5 -1
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.d.ts +10 -0
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.js +95 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.d.ts +9 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.js +112 -0
- package/dist/db-content-api/utilities/where.d.ts +2 -6
- package/dist/db-content-api/utilities/where.js +20 -9
- package/dist/exports/views.d.ts +1 -0
- package/dist/exports/views.js +1 -0
- package/dist/imageSizeOptions.d.ts +23 -0
- package/dist/imageSizeOptions.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/lib/write-agent-config.d.ts +12 -0
- package/dist/lib/write-agent-config.js +52 -0
- package/dist/oauth/defaults.d.ts +1 -0
- package/dist/oauth/defaults.js +1 -0
- package/dist/oauth/endpoints/getLoginEndpoint.js +7 -2
- package/dist/oauth/index.js +24 -9
- package/dist/oauth/utilities/getAdminCollectionSlug.d.ts +1 -1
- package/dist/oauth/utilities/getAuthorizeURL.d.ts +3 -0
- package/dist/oauth/utilities/getAuthorizeURL.js +8 -2
- package/dist/plugin/build-config.js +22 -9
- package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +2 -3
- package/dist/plugin/cloud-limits/CloudLimitModal/index.js +4 -16
- package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +5 -0
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +0 -3
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +65 -124
- package/dist/plugin/cloud-limits/LimitReachedView/index.css +14 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.d.ts +4 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.js +52 -0
- package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +7 -2
- package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +0 -8
- package/dist/plugin/cloud-limits/SlugTracker/index.js +14 -10
- package/dist/plugin/cloud-limits/routes.d.ts +2 -0
- package/dist/plugin/cloud-limits/routes.js +4 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.d.ts +9 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.js +28 -0
- package/dist/plugin/cloud-limits/shared.d.ts +1 -1
- package/dist/plugin/cloud-limits/shared.js +25 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.d.ts +2 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.js +134 -0
- package/dist/plugin/cloud-limits/utilities/toasts.d.ts +4 -0
- package/dist/plugin/cloud-limits/utilities/toasts.js +19 -0
- package/dist/skills/deploy-to-figma/SKILL.md +234 -0
- package/dist/skills/deploy-to-figma/references/cli-reference.md +146 -0
- package/dist/types.d.ts +9 -0
- package/dist/utils/lambda-config.js +4 -14
- package/dist/utils/messages.js +22 -3
- package/dist/utils/payload-generate.d.ts +10 -0
- package/dist/utils/payload-generate.js +59 -0
- package/dist/utils/pnpm-builds.d.ts +14 -0
- package/dist/utils/pnpm-builds.js +125 -0
- package/package.json +9 -1
- package/dist/lib/download-skill.d.ts +0 -12
- package/dist/lib/download-skill.js +0 -77
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AuthCredential } from '../auth/credentials.js';
|
|
2
|
+
interface CheckProjectSubdomainAvailabilityArgs {
|
|
3
|
+
credential: AuthCredential;
|
|
4
|
+
subdomain: string;
|
|
5
|
+
}
|
|
6
|
+
interface ProjectSubdomainAvailability {
|
|
7
|
+
available: boolean;
|
|
8
|
+
domainName: string;
|
|
9
|
+
reason: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function checkProjectSubdomainAvailability({ credential, subdomain, }: CheckProjectSubdomainAvailabilityArgs): Promise<ProjectSubdomainAvailability>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getAuthHeaders } from '../auth/credentials.js';
|
|
2
|
+
import { getEnvConfig } from '../constants.js';
|
|
3
|
+
import { controlPlaneFetch } from './control-plane-fetch.js';
|
|
4
|
+
export async function checkProjectSubdomainAvailability({ credential, subdomain }) {
|
|
5
|
+
if (process.env.FIGMA_MOCK_CONTROL_PLANE === 'true') {
|
|
6
|
+
return {
|
|
7
|
+
available: true,
|
|
8
|
+
domainName: `${subdomain}.pung.site`,
|
|
9
|
+
reason: ''
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const response = await controlPlaneFetch({
|
|
13
|
+
context: 'check project subdomain availability',
|
|
14
|
+
options: {
|
|
15
|
+
body: JSON.stringify({
|
|
16
|
+
subdomain
|
|
17
|
+
}),
|
|
18
|
+
headers: {
|
|
19
|
+
...getAuthHeaders(credential),
|
|
20
|
+
'Content-Type': 'application/json'
|
|
21
|
+
},
|
|
22
|
+
method: 'POST'
|
|
23
|
+
},
|
|
24
|
+
url: `${getEnvConfig().apiBaseUrl}/v1/cms/check_domain_availability`
|
|
25
|
+
});
|
|
26
|
+
const availabilityResponse = await response.json();
|
|
27
|
+
return {
|
|
28
|
+
available: availabilityResponse.meta.available,
|
|
29
|
+
domainName: availabilityResponse.meta.domain_name,
|
|
30
|
+
reason: availabilityResponse.meta.reason
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Error thrown when Control Plane API calls fail. */
|
|
2
|
+
export declare class ControlPlaneError extends Error {
|
|
3
|
+
statusCode?: number | undefined;
|
|
4
|
+
cause?: Error | undefined;
|
|
5
|
+
constructor(message: string, statusCode?: number | undefined, cause?: Error | undefined);
|
|
6
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Error thrown when Control Plane API calls fail. */ export class ControlPlaneError extends Error {
|
|
2
|
+
statusCode;
|
|
3
|
+
cause;
|
|
4
|
+
constructor(message, statusCode, cause){
|
|
5
|
+
super(message), this.statusCode = statusCode, this.cause = cause;
|
|
6
|
+
this.name = 'ControlPlaneError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ControlPlaneError } from './control-plane-error.js';
|
|
2
|
+
import { parseControlPlaneApiError } from './error-response.js';
|
|
3
|
+
const MAX_RETRIES = 3;
|
|
4
|
+
const INITIAL_RETRY_DELAY = 1000;
|
|
5
|
+
export async function controlPlaneFetch({ context, options, url }) {
|
|
6
|
+
let lastError;
|
|
7
|
+
for(let attempt = 0; attempt < MAX_RETRIES; attempt++){
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch(url, options);
|
|
10
|
+
if (response.ok) {
|
|
11
|
+
return response;
|
|
12
|
+
}
|
|
13
|
+
if (response.status < 500) {
|
|
14
|
+
throw new ControlPlaneError(await parseControlPlaneApiError({
|
|
15
|
+
context,
|
|
16
|
+
response
|
|
17
|
+
}), response.status);
|
|
18
|
+
}
|
|
19
|
+
lastError = new ControlPlaneError(await parseControlPlaneApiError({
|
|
20
|
+
context,
|
|
21
|
+
response
|
|
22
|
+
}), response.status);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error instanceof ControlPlaneError) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
lastError = error instanceof Error ? error : new Error('Unknown error');
|
|
28
|
+
}
|
|
29
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
30
|
+
await sleep(INITIAL_RETRY_DELAY * Math.pow(2, attempt));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw new ControlPlaneError(`Network error while ${context}: ${lastError?.message || 'Unknown error'}`, undefined, lastError);
|
|
34
|
+
}
|
|
35
|
+
function sleep(ms) {
|
|
36
|
+
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
37
|
+
}
|
|
@@ -5,14 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { AuthCredential } from '../auth/credentials.js';
|
|
7
7
|
import type { Tenant } from '../types/config.js';
|
|
8
|
-
/**
|
|
9
|
-
* Error thrown when Control Plane API calls fail
|
|
10
|
-
*/
|
|
11
|
-
export declare class ControlPlaneError extends Error {
|
|
12
|
-
statusCode?: number | undefined;
|
|
13
|
-
cause?: Error | undefined;
|
|
14
|
-
constructor(message: string, statusCode?: number | undefined, cause?: Error | undefined);
|
|
15
|
-
}
|
|
16
8
|
/**
|
|
17
9
|
* Options for creating a new tenant
|
|
18
10
|
*/
|
|
@@ -5,22 +5,12 @@
|
|
|
5
5
|
*/ import { getAuthHeaders } from '../auth/credentials.js';
|
|
6
6
|
import { getEnvConfig } from '../constants.js';
|
|
7
7
|
import * as log from '../utils/log.js';
|
|
8
|
-
import {
|
|
8
|
+
import { controlPlaneFetch } from './control-plane-fetch.js';
|
|
9
9
|
/**
|
|
10
10
|
* Get the Control Plane API base URL based on environment
|
|
11
11
|
*/ function getControlPlaneBaseUrl() {
|
|
12
12
|
return getEnvConfig().apiBaseUrl;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Error thrown when Control Plane API calls fail
|
|
16
|
-
*/ export class ControlPlaneError extends Error {
|
|
17
|
-
statusCode;
|
|
18
|
-
cause;
|
|
19
|
-
constructor(message, statusCode, cause){
|
|
20
|
-
super(message), this.statusCode = statusCode, this.cause = cause;
|
|
21
|
-
this.name = 'ControlPlaneError';
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
14
|
/**
|
|
25
15
|
* List all tenant instances for the authenticated user
|
|
26
16
|
* Maps to: GET /v1/tenant
|
|
@@ -316,39 +306,3 @@ import { parseControlPlaneApiError } from './error-response.js';
|
|
|
316
306
|
const data = await response.json();
|
|
317
307
|
return data.meta.cms_resource_id;
|
|
318
308
|
}
|
|
319
|
-
const MAX_RETRIES = 3;
|
|
320
|
-
const INITIAL_RETRY_DELAY = 1000;
|
|
321
|
-
function sleep(ms) {
|
|
322
|
-
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
323
|
-
}
|
|
324
|
-
async function controlPlaneFetch(params) {
|
|
325
|
-
const { context, options, url } = params;
|
|
326
|
-
let lastError;
|
|
327
|
-
for(let attempt = 0; attempt < MAX_RETRIES; attempt++){
|
|
328
|
-
try {
|
|
329
|
-
const response = await fetch(url, options);
|
|
330
|
-
if (response.ok) {
|
|
331
|
-
return response;
|
|
332
|
-
}
|
|
333
|
-
if (response.status < 500) {
|
|
334
|
-
throw new ControlPlaneError(await parseControlPlaneApiError({
|
|
335
|
-
context,
|
|
336
|
-
response
|
|
337
|
-
}), response.status);
|
|
338
|
-
}
|
|
339
|
-
lastError = new ControlPlaneError(await parseControlPlaneApiError({
|
|
340
|
-
context,
|
|
341
|
-
response
|
|
342
|
-
}), response.status);
|
|
343
|
-
} catch (error) {
|
|
344
|
-
if (error instanceof ControlPlaneError) {
|
|
345
|
-
throw error;
|
|
346
|
-
}
|
|
347
|
-
lastError = error instanceof Error ? error : new Error('Unknown error');
|
|
348
|
-
}
|
|
349
|
-
if (attempt < MAX_RETRIES - 1) {
|
|
350
|
-
await sleep(INITIAL_RETRY_DELAY * Math.pow(2, attempt));
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
throw new ControlPlaneError(`Network error while ${context}: ${lastError?.message || 'Unknown error'}`, undefined, lastError);
|
|
354
|
-
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AuthCredential } from '../auth/credentials.js';
|
|
2
|
+
import type { ProjectResourceType } from './project-resource-type.js';
|
|
3
|
+
interface CreateProjectArgs {
|
|
4
|
+
credential: AuthCredential;
|
|
5
|
+
description: string;
|
|
6
|
+
name: string;
|
|
7
|
+
parentPlanKey: string;
|
|
8
|
+
region: string;
|
|
9
|
+
resourceType: ProjectResourceType;
|
|
10
|
+
subdomain: string;
|
|
11
|
+
}
|
|
12
|
+
interface CreateProjectResponse {
|
|
13
|
+
domainName: string;
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function createProject({ name, credential, description, parentPlanKey, region, resourceType, subdomain, }: CreateProjectArgs): Promise<CreateProjectResponse>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getAuthHeaders } from '../auth/credentials.js';
|
|
2
|
+
import { getEnvConfig } from '../constants.js';
|
|
3
|
+
import { controlPlaneFetch } from './control-plane-fetch.js';
|
|
4
|
+
export async function createProject({ name, credential, description, parentPlanKey, region, resourceType, subdomain }) {
|
|
5
|
+
if (process.env.FIGMA_MOCK_CONTROL_PLANE === 'true') {
|
|
6
|
+
return {
|
|
7
|
+
id: `cms_${Math.random().toString(36).substring(2, 11)}`,
|
|
8
|
+
name,
|
|
9
|
+
domainName: `${subdomain}.figmasource.site`
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const response = await controlPlaneFetch({
|
|
13
|
+
context: 'create project',
|
|
14
|
+
options: {
|
|
15
|
+
body: JSON.stringify({
|
|
16
|
+
name,
|
|
17
|
+
description,
|
|
18
|
+
parent_plan_key: parentPlanKey,
|
|
19
|
+
region,
|
|
20
|
+
resource_type: resourceType,
|
|
21
|
+
subdomain
|
|
22
|
+
}),
|
|
23
|
+
headers: {
|
|
24
|
+
...getAuthHeaders(credential),
|
|
25
|
+
'Content-Type': 'application/json'
|
|
26
|
+
},
|
|
27
|
+
method: 'POST'
|
|
28
|
+
},
|
|
29
|
+
url: `${getEnvConfig().apiBaseUrl}/v1/cms`
|
|
30
|
+
});
|
|
31
|
+
const projectResponse = await response.json();
|
|
32
|
+
return {
|
|
33
|
+
id: projectResponse.meta.tenant.id,
|
|
34
|
+
name: projectResponse.meta.tenant.name,
|
|
35
|
+
domainName: projectResponse.meta.tenant.domain_name
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AuthCredential } from '../auth/credentials.js';
|
|
2
|
+
interface ProjectWorkspace {
|
|
3
|
+
isDefault: boolean;
|
|
4
|
+
name: string;
|
|
5
|
+
planKey: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function listProjectWorkspaces(credential: AuthCredential): Promise<ProjectWorkspace[]>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getAuthHeaders } from '../auth/credentials.js';
|
|
2
|
+
import { getEnvConfig } from '../constants.js';
|
|
3
|
+
import { controlPlaneFetch } from './control-plane-fetch.js';
|
|
4
|
+
export async function listProjectWorkspaces(credential) {
|
|
5
|
+
if (process.env.FIGMA_MOCK_CONTROL_PLANE === 'true') {
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
name: 'Mock workspace',
|
|
9
|
+
isDefault: true,
|
|
10
|
+
planKey: 'team::mock-workspace'
|
|
11
|
+
}
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
const response = await controlPlaneFetch({
|
|
15
|
+
context: 'list project workspaces',
|
|
16
|
+
options: {
|
|
17
|
+
headers: getAuthHeaders(credential),
|
|
18
|
+
method: 'POST'
|
|
19
|
+
},
|
|
20
|
+
url: `${getEnvConfig().apiBaseUrl}/v1/cms/workspaces`
|
|
21
|
+
});
|
|
22
|
+
const workspaceResponse = await response.json();
|
|
23
|
+
return workspaceResponse.meta.workspaces.map((workspace)=>({
|
|
24
|
+
name: workspace.name,
|
|
25
|
+
isDefault: workspace.is_default,
|
|
26
|
+
planKey: workspace.plan_key
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ProjectResourceType = 'payload' | 'vite';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TokenStore } from './token-store.js';
|
|
2
|
+
export interface StartHeadlessOAuthFlowResult {
|
|
3
|
+
authorizationUrl: string;
|
|
4
|
+
expiresAt: number;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class HeadlessOAuthFlowError extends Error {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
/** Start OAuth without a callback listener so another process can resume it. */
|
|
11
|
+
export declare function startHeadlessOAuthFlow(tokenStore: TokenStore, options?: {
|
|
12
|
+
now?: number;
|
|
13
|
+
}): StartHeadlessOAuthFlowResult;
|
|
14
|
+
/** Complete a previously started headless OAuth flow and store its tokens. */
|
|
15
|
+
export declare function completeHeadlessOAuthFlow({ callbackUrl, now, sessionId, tokenStore, }: {
|
|
16
|
+
callbackUrl: string;
|
|
17
|
+
now?: number;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
tokenStore: TokenStore;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
tokens: import("./types.js").FigmaTokens;
|
|
22
|
+
userId: string | undefined;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { getOAuthConfig } from '../config/oauth.js';
|
|
3
|
+
import { buildAuthorizationUrl } from './browser.js';
|
|
4
|
+
import { parsePastedCode } from './paste-code.js';
|
|
5
|
+
import { generatePKCEPair } from './pkce.js';
|
|
6
|
+
import { exchangeCodeForTokens } from './refresh.js';
|
|
7
|
+
const HEADLESS_OAUTH_SESSION_TTL_MS = 10 * 60 * 1000;
|
|
8
|
+
export class HeadlessOAuthFlowError extends Error {
|
|
9
|
+
constructor(message){
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'HeadlessOAuthFlowError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Start OAuth without a callback listener so another process can resume it. */ export function startHeadlessOAuthFlow(tokenStore, options = {}) {
|
|
15
|
+
const oauthConfig = getOAuthConfig();
|
|
16
|
+
const { codeChallenge, codeChallengeMethod, codeVerifier } = generatePKCEPair();
|
|
17
|
+
const now = options.now ?? Date.now();
|
|
18
|
+
const session = {
|
|
19
|
+
id: crypto.randomBytes(32).toString('base64url'),
|
|
20
|
+
clientId: oauthConfig.clientId,
|
|
21
|
+
codeVerifier,
|
|
22
|
+
expiresAt: now + HEADLESS_OAUTH_SESSION_TTL_MS,
|
|
23
|
+
redirectUri: oauthConfig.redirectUri,
|
|
24
|
+
state: crypto.randomBytes(32).toString('base64url')
|
|
25
|
+
};
|
|
26
|
+
tokenStore.setHeadlessOAuthSession(session);
|
|
27
|
+
return {
|
|
28
|
+
authorizationUrl: buildAuthorizationUrl({
|
|
29
|
+
authorizationUrl: oauthConfig.authorizationUrl,
|
|
30
|
+
clientId: session.clientId,
|
|
31
|
+
codeChallenge,
|
|
32
|
+
codeChallengeMethod,
|
|
33
|
+
redirectUri: session.redirectUri,
|
|
34
|
+
scopes: oauthConfig.scopes,
|
|
35
|
+
state: session.state
|
|
36
|
+
}),
|
|
37
|
+
expiresAt: session.expiresAt,
|
|
38
|
+
sessionId: session.id
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Complete a previously started headless OAuth flow and store its tokens. */ export async function completeHeadlessOAuthFlow({ callbackUrl, now = Date.now(), sessionId, tokenStore }) {
|
|
42
|
+
const session = tokenStore.getHeadlessOAuthSession();
|
|
43
|
+
if (!session) {
|
|
44
|
+
throw new HeadlessOAuthFlowError('No pending login found. Start login again with --headless.');
|
|
45
|
+
}
|
|
46
|
+
if (session.id !== sessionId) {
|
|
47
|
+
throw new HeadlessOAuthFlowError('Login session does not match. Start login again with --headless.');
|
|
48
|
+
}
|
|
49
|
+
if (now >= session.expiresAt) {
|
|
50
|
+
tokenStore.clearHeadlessOAuthSession();
|
|
51
|
+
throw new HeadlessOAuthFlowError('Login session expired. Start login again with --headless.');
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
try {
|
|
55
|
+
new URL(callbackUrl);
|
|
56
|
+
} catch {
|
|
57
|
+
throw new HeadlessOAuthFlowError('Paste the full redirected URL so the login state can be verified.');
|
|
58
|
+
}
|
|
59
|
+
const code = parsePastedCode(callbackUrl, session.state);
|
|
60
|
+
const tokens = await exchangeCodeForTokens(code, session.codeVerifier, session.redirectUri, session.clientId);
|
|
61
|
+
tokenStore.setTokens(tokens);
|
|
62
|
+
return {
|
|
63
|
+
tokens,
|
|
64
|
+
userId: tokens.userId
|
|
65
|
+
};
|
|
66
|
+
} finally{
|
|
67
|
+
tokenStore.clearHeadlessOAuthSession();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -16,6 +16,8 @@ export interface OAuthFlowOptions {
|
|
|
16
16
|
clientId?: string;
|
|
17
17
|
/** Callback server fallback ports to try after port */
|
|
18
18
|
fallbackPorts?: number[];
|
|
19
|
+
/** Open the browser without asking for confirmation */
|
|
20
|
+
openBrowserWithoutPrompt?: boolean;
|
|
19
21
|
/** First callback server port to try */
|
|
20
22
|
port?: number;
|
|
21
23
|
/** Redirect URI (defaults to config) */
|
package/dist/auth/oauth-flow.js
CHANGED
|
@@ -54,7 +54,7 @@ import { exchangeCodeForTokens, refreshAccessToken, TokenRefreshError } from './
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
// REAL OAUTH IMPLEMENTATION
|
|
57
|
-
const { clientId = oauthConfig.clientId, fallbackPorts, port, redirectUri = oauthConfig.redirectUri, scopes = oauthConfig.scopes, timeoutMs = 120000 } = options;
|
|
57
|
+
const { clientId = oauthConfig.clientId, fallbackPorts, openBrowserWithoutPrompt = false, port, redirectUri = oauthConfig.redirectUri, scopes = oauthConfig.scopes, timeoutMs = 120000 } = options;
|
|
58
58
|
// Generate PKCE pair for enhanced security
|
|
59
59
|
const { codeChallenge, codeChallengeMethod, codeVerifier } = generatePKCEPair();
|
|
60
60
|
// Generate random state for CSRF protection
|
|
@@ -85,13 +85,18 @@ import { exchangeCodeForTokens, refreshAccessToken, TokenRefreshError } from './
|
|
|
85
85
|
scopes,
|
|
86
86
|
state
|
|
87
87
|
});
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
// Ask interactive callers before opening a browser. Automated callers have
|
|
89
|
+
// already accepted defaults with -y, so proceed directly to authorization.
|
|
90
|
+
let shouldOpenBrowser = openBrowserWithoutPrompt;
|
|
91
|
+
if (!openBrowserWithoutPrompt) {
|
|
92
|
+
const browserConfirmation = await p.confirm({
|
|
93
|
+
initialValue: true,
|
|
94
|
+
message: 'Open browser to authenticate with Figma?'
|
|
95
|
+
});
|
|
96
|
+
if (p.isCancel(browserConfirmation)) {
|
|
97
|
+
throw new OAuthFlowError('Authentication cancelled by user');
|
|
98
|
+
}
|
|
99
|
+
shouldOpenBrowser = browserConfirmation;
|
|
95
100
|
}
|
|
96
101
|
let browserOpened = false;
|
|
97
102
|
if (shouldOpenBrowser) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Environment } from '../constants.js';
|
|
2
|
-
import type { BootstrapData, FigmaTokens, ProjectStoreScope, ProjectToken, TokenStoreConfig } from './types.js';
|
|
2
|
+
import type { BootstrapData, FigmaTokens, HeadlessOAuthSession, ProjectStoreScope, ProjectToken, TokenStoreConfig } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Get the singleton TokenStore instance for an environment.
|
|
5
5
|
*/
|
|
@@ -23,6 +23,9 @@ export declare class TokenStore {
|
|
|
23
23
|
getOauthInfo(): FigmaTokens | null;
|
|
24
24
|
setTokens(tokens: FigmaTokens): void;
|
|
25
25
|
clearTokens(): void;
|
|
26
|
+
getHeadlessOAuthSession(): HeadlessOAuthSession | null;
|
|
27
|
+
setHeadlessOAuthSession(session: HeadlessOAuthSession): void;
|
|
28
|
+
clearHeadlessOAuthSession(): void;
|
|
26
29
|
hasValidOauthToken(): boolean;
|
|
27
30
|
isOauthExpired(): boolean;
|
|
28
31
|
getCurrentOauthToken(): null | string;
|
package/dist/auth/token-store.js
CHANGED
|
@@ -186,11 +186,25 @@ const PROJECT_STORE_SCHEMA_VERSION = 1;
|
|
|
186
186
|
clearTokens() {
|
|
187
187
|
this.oauthConfig.delete('accessToken');
|
|
188
188
|
this.oauthConfig.delete('expiresAt');
|
|
189
|
+
this.oauthConfig.delete('headlessOAuthSession');
|
|
189
190
|
this.oauthConfig.delete('refreshToken');
|
|
190
191
|
this.oauthConfig.delete('scopes');
|
|
191
192
|
this.oauthConfig.delete('tokenType');
|
|
192
193
|
this.oauthConfig.delete('userId');
|
|
193
194
|
}
|
|
195
|
+
getHeadlessOAuthSession() {
|
|
196
|
+
return this.safeGet({
|
|
197
|
+
config: this.oauthConfig,
|
|
198
|
+
key: 'headlessOAuthSession'
|
|
199
|
+
}) ?? null;
|
|
200
|
+
}
|
|
201
|
+
setHeadlessOAuthSession(session) {
|
|
202
|
+
this.oauthConfig.set('_schemaVersion', OAUTH_STORE_SCHEMA_VERSION);
|
|
203
|
+
this.oauthConfig.set('headlessOAuthSession', session);
|
|
204
|
+
}
|
|
205
|
+
clearHeadlessOAuthSession() {
|
|
206
|
+
this.oauthConfig.delete('headlessOAuthSession');
|
|
207
|
+
}
|
|
194
208
|
hasValidOauthToken() {
|
|
195
209
|
const tokens = this.getOauthInfo();
|
|
196
210
|
if (!tokens) {
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -32,6 +32,18 @@ export interface FigmaTokens {
|
|
|
32
32
|
/** Figma user ID string */
|
|
33
33
|
userId?: string;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Temporary state for a resumable OAuth login. Stored encrypted on the machine
|
|
37
|
+
* that generated the PKCE verifier and removed after completion or expiry.
|
|
38
|
+
*/
|
|
39
|
+
export interface HeadlessOAuthSession {
|
|
40
|
+
clientId: string;
|
|
41
|
+
codeVerifier: string;
|
|
42
|
+
expiresAt: number;
|
|
43
|
+
id: string;
|
|
44
|
+
redirectUri: string;
|
|
45
|
+
state: string;
|
|
46
|
+
}
|
|
35
47
|
/**
|
|
36
48
|
* Raw token response from Figma OAuth2 token endpoint
|
|
37
49
|
* Response from POST https://api.figma.com/v1/oauth/token
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
export declare const cliArgSpec: {
|
|
2
2
|
readonly '--adapter': StringConstructor;
|
|
3
3
|
readonly '--all': BooleanConstructor;
|
|
4
|
+
readonly '--callback-url': StringConstructor;
|
|
5
|
+
readonly '--complete': StringConstructor;
|
|
4
6
|
readonly '--debug': BooleanConstructor;
|
|
7
|
+
readonly '--description': StringConstructor;
|
|
5
8
|
readonly '--dry-run': BooleanConstructor;
|
|
6
9
|
readonly '--env': StringConstructor;
|
|
7
10
|
readonly '--force': BooleanConstructor;
|
|
11
|
+
readonly '--global': BooleanConstructor;
|
|
12
|
+
readonly '--headless': BooleanConstructor;
|
|
8
13
|
readonly '--help': BooleanConstructor;
|
|
9
14
|
readonly '--id': StringConstructor;
|
|
10
15
|
readonly '--infra-env': StringConstructor;
|
|
11
16
|
readonly '--json': BooleanConstructor;
|
|
12
17
|
readonly '--name': StringConstructor;
|
|
13
|
-
readonly '--no-
|
|
18
|
+
readonly '--no-agent': BooleanConstructor;
|
|
14
19
|
readonly '--output': StringConstructor;
|
|
15
20
|
readonly '--payload-version': StringConstructor;
|
|
21
|
+
readonly '--project': BooleanConstructor;
|
|
22
|
+
readonly '--region': StringConstructor;
|
|
16
23
|
readonly '--skip-auth': BooleanConstructor;
|
|
17
24
|
readonly '--skip-build': BooleanConstructor;
|
|
18
25
|
readonly '--skip-size-check': BooleanConstructor;
|
|
26
|
+
readonly '--subdomain': StringConstructor;
|
|
19
27
|
readonly '--template': StringConstructor;
|
|
20
28
|
readonly '--version': BooleanConstructor;
|
|
29
|
+
readonly '--workspace': StringConstructor;
|
|
21
30
|
readonly '--yes': BooleanConstructor;
|
|
22
31
|
readonly '--environment': "--env";
|
|
23
32
|
readonly '-e': "--env";
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { initCommand } from './commands/init.js';
|
|
|
11
11
|
import { listTokensCommand } from './commands/list-tokens.js';
|
|
12
12
|
import { loginCommand } from './commands/login.js';
|
|
13
13
|
import { logoutCommand } from './commands/logout.js';
|
|
14
|
+
import { setupAgentCommand } from './commands/setup-agent.js';
|
|
14
15
|
import { upgradeCommand } from './commands/upgrade.js';
|
|
15
16
|
import { uploadSchemaCommand } from './commands/upload-schema.js';
|
|
16
17
|
import { setInfraEnvironment } from './constants.js';
|
|
@@ -18,23 +19,32 @@ import { helpMessage } from './utils/messages.js';
|
|
|
18
19
|
export const cliArgSpec = {
|
|
19
20
|
'--adapter': String,
|
|
20
21
|
'--all': Boolean,
|
|
22
|
+
'--callback-url': String,
|
|
23
|
+
'--complete': String,
|
|
21
24
|
'--debug': Boolean,
|
|
25
|
+
'--description': String,
|
|
22
26
|
'--dry-run': Boolean,
|
|
23
27
|
'--env': String,
|
|
24
28
|
'--force': Boolean,
|
|
29
|
+
'--global': Boolean,
|
|
30
|
+
'--headless': Boolean,
|
|
25
31
|
'--help': Boolean,
|
|
26
32
|
'--id': String,
|
|
27
33
|
'--infra-env': String,
|
|
28
34
|
'--json': Boolean,
|
|
29
35
|
'--name': String,
|
|
30
|
-
'--no-
|
|
36
|
+
'--no-agent': Boolean,
|
|
31
37
|
'--output': String,
|
|
32
38
|
'--payload-version': String,
|
|
39
|
+
'--project': Boolean,
|
|
40
|
+
'--region': String,
|
|
33
41
|
'--skip-auth': Boolean,
|
|
34
42
|
'--skip-build': Boolean,
|
|
35
43
|
'--skip-size-check': Boolean,
|
|
44
|
+
'--subdomain': String,
|
|
36
45
|
'--template': String,
|
|
37
46
|
'--version': Boolean,
|
|
47
|
+
'--workspace': String,
|
|
38
48
|
'--yes': Boolean,
|
|
39
49
|
// Aliases
|
|
40
50
|
'--environment': '--env',
|
|
@@ -104,7 +114,16 @@ class Main {
|
|
|
104
114
|
helpMessage();
|
|
105
115
|
process.exit(0);
|
|
106
116
|
}
|
|
107
|
-
//
|
|
117
|
+
// JSON and debug commands output plain text — skip styled intro.
|
|
118
|
+
if (subcommand === 'login' && this.args['--json']) {
|
|
119
|
+
await loginCommand({
|
|
120
|
+
callbackUrl: this.args['--callback-url'],
|
|
121
|
+
completeSessionId: this.args['--complete'],
|
|
122
|
+
headless: this.args['--headless'],
|
|
123
|
+
json: true
|
|
124
|
+
});
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
108
127
|
if (subcommand === 'debug') {
|
|
109
128
|
await debugCommand();
|
|
110
129
|
process.exit(0);
|
|
@@ -132,12 +151,17 @@ class Main {
|
|
|
132
151
|
case 'deploy':
|
|
133
152
|
await deployCommand({
|
|
134
153
|
id: this.args['--id'],
|
|
154
|
+
name: this.args['--name'],
|
|
135
155
|
adapter: this.args['--adapter'],
|
|
136
156
|
debug: this.args['--debug'],
|
|
157
|
+
description: this.args['--description'],
|
|
137
158
|
env: this.args['--env'],
|
|
138
159
|
output: this.args['--output'],
|
|
160
|
+
region: this.args['--region'],
|
|
139
161
|
skipBuild: this.args['--skip-build'],
|
|
140
162
|
skipSizeCheck: this.args['--skip-size-check'],
|
|
163
|
+
subdomain: this.args['--subdomain'],
|
|
164
|
+
workspace: this.args['--workspace'],
|
|
141
165
|
yes: this.args['--yes']
|
|
142
166
|
});
|
|
143
167
|
break;
|
|
@@ -153,7 +177,7 @@ class Main {
|
|
|
153
177
|
debug: this.args['--debug'],
|
|
154
178
|
env: this.args['--env'],
|
|
155
179
|
force: this.args['--force'],
|
|
156
|
-
|
|
180
|
+
noAgent: this.args['--no-agent'],
|
|
157
181
|
payloadVersion: this.args['--payload-version'],
|
|
158
182
|
skipAuth: this.args['--skip-auth'],
|
|
159
183
|
template: this.args['--template']
|
|
@@ -166,7 +190,11 @@ class Main {
|
|
|
166
190
|
});
|
|
167
191
|
break;
|
|
168
192
|
case 'login':
|
|
169
|
-
await loginCommand(
|
|
193
|
+
await loginCommand({
|
|
194
|
+
callbackUrl: this.args['--callback-url'],
|
|
195
|
+
completeSessionId: this.args['--complete'],
|
|
196
|
+
headless: this.args['--headless']
|
|
197
|
+
});
|
|
170
198
|
break;
|
|
171
199
|
case 'logout':
|
|
172
200
|
await logoutCommand({
|
|
@@ -174,6 +202,13 @@ class Main {
|
|
|
174
202
|
debug: this.args['--debug']
|
|
175
203
|
});
|
|
176
204
|
break;
|
|
205
|
+
case 'setup-agent':
|
|
206
|
+
await setupAgentCommand({
|
|
207
|
+
global: this.args['--global'],
|
|
208
|
+
project: this.args['--project'],
|
|
209
|
+
yes: this.args['--yes']
|
|
210
|
+
});
|
|
211
|
+
break;
|
|
177
212
|
case 'upgrade':
|
|
178
213
|
await upgradeCommand({
|
|
179
214
|
isDryRun: this.args['--dry-run']
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
import {
|
|
3
|
+
import { getBootstrapInfo } from '../api/control-plane.js';
|
|
4
|
+
import { ControlPlaneError } from '../api/control-plane-error.js';
|
|
4
5
|
import { getValidCredential } from '../auth/oauth-flow.js';
|
|
5
6
|
import { getTokenStore } from '../auth/token-store.js';
|
|
6
7
|
import { getInfraEnvironment } from '../constants.js';
|