@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.
Files changed (116) hide show
  1. package/dist/api/check-project-subdomain-availability.d.ts +12 -0
  2. package/dist/api/check-project-subdomain-availability.js +32 -0
  3. package/dist/api/control-plane-error.d.ts +6 -0
  4. package/dist/api/control-plane-error.js +8 -0
  5. package/dist/api/control-plane-fetch.d.ts +7 -0
  6. package/dist/api/control-plane-fetch.js +37 -0
  7. package/dist/api/control-plane.d.ts +0 -8
  8. package/dist/api/control-plane.js +1 -47
  9. package/dist/api/create-project.d.ts +18 -0
  10. package/dist/api/create-project.js +37 -0
  11. package/dist/api/list-project-workspaces.d.ts +8 -0
  12. package/dist/api/list-project-workspaces.js +28 -0
  13. package/dist/api/project-resource-type.d.ts +1 -0
  14. package/dist/api/project-resource-type.js +1 -0
  15. package/dist/auth/headless-oauth-flow.d.ts +23 -0
  16. package/dist/auth/headless-oauth-flow.js +69 -0
  17. package/dist/auth/oauth-flow.d.ts +2 -0
  18. package/dist/auth/oauth-flow.js +13 -8
  19. package/dist/auth/token-store.d.ts +4 -1
  20. package/dist/auth/token-store.js +14 -0
  21. package/dist/auth/types.d.ts +12 -0
  22. package/dist/cli.d.ts +10 -1
  23. package/dist/cli.js +39 -4
  24. package/dist/commands/bootstrap.js +2 -1
  25. package/dist/commands/deploy.d.ts +10 -0
  26. package/dist/commands/deploy.js +39 -11
  27. package/dist/commands/detect-project-resource-type.d.ts +3 -0
  28. package/dist/commands/detect-project-resource-type.js +28 -0
  29. package/dist/commands/init.d.ts +2 -2
  30. package/dist/commands/init.js +163 -78
  31. package/dist/commands/login.d.ts +11 -1
  32. package/dist/commands/login.js +66 -4
  33. package/dist/commands/prompt-to-create-project/format-project-subdomain.d.ts +1 -0
  34. package/dist/commands/prompt-to-create-project/format-project-subdomain.js +3 -0
  35. package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.d.ts +1 -0
  36. package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.js +7 -0
  37. package/dist/commands/prompt-to-create-project/get-default-project-name.d.ts +1 -0
  38. package/dist/commands/prompt-to-create-project/get-default-project-name.js +13 -0
  39. package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.d.ts +7 -0
  40. package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.js +32 -0
  41. package/dist/commands/prompt-to-create-project/resolve-project-region.d.ts +8 -0
  42. package/dist/commands/prompt-to-create-project/resolve-project-region.js +27 -0
  43. package/dist/commands/prompt-to-create-project/resolve-project-subdomain.d.ts +9 -0
  44. package/dist/commands/prompt-to-create-project/resolve-project-subdomain.js +54 -0
  45. package/dist/commands/prompt-to-create-project/resolve-text-input.d.ts +9 -0
  46. package/dist/commands/prompt-to-create-project/resolve-text-input.js +23 -0
  47. package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.d.ts +12 -0
  48. package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.js +36 -0
  49. package/dist/commands/prompt-to-create-project/validate-subdomain.d.ts +1 -0
  50. package/dist/commands/prompt-to-create-project/validate-subdomain.js +9 -0
  51. package/dist/commands/prompt-to-create-project.d.ts +24 -0
  52. package/dist/commands/prompt-to-create-project.js +69 -0
  53. package/dist/commands/setup-agent.d.ts +12 -0
  54. package/dist/commands/setup-agent.js +123 -0
  55. package/dist/commands/write-project-configuration.d.ts +12 -0
  56. package/dist/commands/write-project-configuration.js +16 -0
  57. package/dist/db-content-api/index.js +52 -33
  58. package/dist/db-content-api/utilities/data/injectGlobalType.d.ts +6 -0
  59. package/dist/db-content-api/utilities/data/injectGlobalType.js +11 -0
  60. package/dist/db-content-api/utilities/isRecord.d.ts +9 -0
  61. package/dist/db-content-api/utilities/isRecord.js +19 -0
  62. package/dist/db-content-api/utilities/joins.js +5 -1
  63. package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.d.ts +10 -0
  64. package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.js +95 -0
  65. package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.d.ts +9 -0
  66. package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.js +112 -0
  67. package/dist/db-content-api/utilities/where.d.ts +2 -6
  68. package/dist/db-content-api/utilities/where.js +20 -9
  69. package/dist/exports/views.d.ts +1 -0
  70. package/dist/exports/views.js +1 -0
  71. package/dist/imageSizeOptions.d.ts +23 -0
  72. package/dist/imageSizeOptions.js +26 -0
  73. package/dist/index.d.ts +1 -0
  74. package/dist/lib/write-agent-config.d.ts +12 -0
  75. package/dist/lib/write-agent-config.js +52 -0
  76. package/dist/oauth/defaults.d.ts +1 -0
  77. package/dist/oauth/defaults.js +1 -0
  78. package/dist/oauth/endpoints/getLoginEndpoint.js +7 -2
  79. package/dist/oauth/index.js +24 -9
  80. package/dist/oauth/utilities/getAdminCollectionSlug.d.ts +1 -1
  81. package/dist/oauth/utilities/getAuthorizeURL.d.ts +3 -0
  82. package/dist/oauth/utilities/getAuthorizeURL.js +8 -2
  83. package/dist/plugin/build-config.js +22 -9
  84. package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +2 -3
  85. package/dist/plugin/cloud-limits/CloudLimitModal/index.js +4 -16
  86. package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +5 -0
  87. package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +0 -3
  88. package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +65 -124
  89. package/dist/plugin/cloud-limits/LimitReachedView/index.css +14 -0
  90. package/dist/plugin/cloud-limits/LimitReachedView/index.d.ts +4 -0
  91. package/dist/plugin/cloud-limits/LimitReachedView/index.js +52 -0
  92. package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +7 -2
  93. package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +0 -8
  94. package/dist/plugin/cloud-limits/SlugTracker/index.js +14 -10
  95. package/dist/plugin/cloud-limits/routes.d.ts +2 -0
  96. package/dist/plugin/cloud-limits/routes.js +4 -0
  97. package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.d.ts +9 -0
  98. package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.js +28 -0
  99. package/dist/plugin/cloud-limits/shared.d.ts +1 -1
  100. package/dist/plugin/cloud-limits/shared.js +25 -0
  101. package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.d.ts +2 -0
  102. package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.js +134 -0
  103. package/dist/plugin/cloud-limits/utilities/toasts.d.ts +4 -0
  104. package/dist/plugin/cloud-limits/utilities/toasts.js +19 -0
  105. package/dist/skills/deploy-to-figma/SKILL.md +234 -0
  106. package/dist/skills/deploy-to-figma/references/cli-reference.md +146 -0
  107. package/dist/types.d.ts +9 -0
  108. package/dist/utils/lambda-config.js +4 -14
  109. package/dist/utils/messages.js +22 -3
  110. package/dist/utils/payload-generate.d.ts +10 -0
  111. package/dist/utils/payload-generate.js +59 -0
  112. package/dist/utils/pnpm-builds.d.ts +14 -0
  113. package/dist/utils/pnpm-builds.js +125 -0
  114. package/package.json +9 -1
  115. package/dist/lib/download-skill.d.ts +0 -12
  116. 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,7 @@
1
+ interface ControlPlaneFetchArgs {
2
+ context: string;
3
+ options: RequestInit;
4
+ url: string;
5
+ }
6
+ export declare function controlPlaneFetch({ context, options, url }: ControlPlaneFetchArgs): Promise<Response>;
7
+ export {};
@@ -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 { parseControlPlaneApiError } from './error-response.js';
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) */
@@ -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
- // Prompt user to open browser
89
- const shouldOpenBrowser = await p.confirm({
90
- initialValue: true,
91
- message: 'Open browser to authenticate with Figma?'
92
- });
93
- if (p.isCancel(shouldOpenBrowser)) {
94
- throw new OAuthFlowError('Authentication cancelled by user');
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;
@@ -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) {
@@ -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-skill': BooleanConstructor;
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-skill': Boolean,
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
- // Debug command outputs plain text for copy-paste — skip styled intro
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
- noSkill: this.args['--no-skill'],
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 { ControlPlaneError, getBootstrapInfo } from '../api/control-plane.js';
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';