@payloadcms/figma 0.0.1-alpha.62 → 0.0.1-alpha.64

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 (42) hide show
  1. package/dist/auth/crypto-utils.d.ts +11 -0
  2. package/dist/auth/crypto-utils.js +51 -0
  3. package/dist/auth/oauth-flow.js +4 -3
  4. package/dist/auth/project-token.d.ts +32 -16
  5. package/dist/auth/project-token.js +165 -64
  6. package/dist/auth/token-store-migration.d.ts +58 -0
  7. package/dist/auth/token-store-migration.js +156 -0
  8. package/dist/auth/token-store.d.ts +51 -128
  9. package/dist/auth/token-store.js +326 -187
  10. package/dist/auth/types.d.ts +12 -1
  11. package/dist/cli.js +15 -0
  12. package/dist/commands/bootstrap.d.ts +18 -0
  13. package/dist/commands/bootstrap.js +90 -0
  14. package/dist/commands/debug.js +16 -12
  15. package/dist/commands/dump-tokens.d.ts +12 -0
  16. package/dist/commands/dump-tokens.js +69 -0
  17. package/dist/commands/init.js +39 -5
  18. package/dist/commands/list-tokens.js +2 -2
  19. package/dist/commands/login.js +1 -1
  20. package/dist/commands/logout.js +23 -4
  21. package/dist/db-content-api/index.js +27 -73
  22. package/dist/db-content-api/utilities/auth.js +4 -1
  23. package/dist/oauth/defaults.d.ts +3 -1
  24. package/dist/oauth/defaults.js +6 -5
  25. package/dist/oauth/endpoints/getLoginEndpoint.js +1 -1
  26. package/dist/oauth/index.js +1 -0
  27. package/dist/oauth/types.d.ts +10 -0
  28. package/dist/plugin/auth-preflight.d.ts +8 -0
  29. package/dist/plugin/auth-preflight.js +20 -0
  30. package/dist/plugin/bootstrap-preflight.d.ts +23 -0
  31. package/dist/plugin/bootstrap-preflight.js +45 -0
  32. package/dist/plugin/build-config.js +135 -58
  33. package/dist/plugin/dev-cookie-names.d.ts +14 -0
  34. package/dist/plugin/dev-cookie-names.js +19 -0
  35. package/dist/storage-content-api/client.js +4 -1
  36. package/dist/utils/messages.js +7 -0
  37. package/dist/utils/payload-config-modifier.js +96 -107
  38. package/dist/utils/payload-package-check.d.ts +21 -1
  39. package/dist/utils/payload-package-check.js +66 -26
  40. package/dist/utils/token-display.d.ts +5 -1
  41. package/dist/utils/token-display.js +47 -26
  42. package/package.json +2 -1
@@ -2,10 +2,11 @@ import { parseCookies } from 'payload';
2
2
  import { v4 as uuid } from 'uuid';
3
3
  import { hasUserTokenPropsChanged } from './utilities/hasUserTokenPropsChanged.js';
4
4
  import { isDuplicateKeyError } from './utilities/isDuplicateKeyError.js';
5
- function getFigmaUserInfo(headers) {
5
+ export const DEFAULT_USER_INFO_COOKIE_NAME = 'figma-user-info';
6
+ function getFigmaUserInfo(headers, cookieName) {
6
7
  try {
7
8
  const cookies = parseCookies(headers);
8
- const encoded = cookies.get('figma-user-info');
9
+ const encoded = cookies.get(cookieName);
9
10
  if (!encoded) {
10
11
  return null;
11
12
  }
@@ -28,7 +29,7 @@ export const defaultUsernameField = {
28
29
  },
29
30
  unique: true
30
31
  };
31
- export const defaultVerify = ({ collection, strategyName, usernameField })=>async ({ headers, payload, token })=>{
32
+ export const defaultVerify = ({ collection, strategyName, userInfoCookieName = DEFAULT_USER_INFO_COOKIE_NAME, usernameField })=>async ({ headers, payload, token })=>{
32
33
  let tokenUsername = 'preferred_username';
33
34
  if (usernameField.name !== 'preferredUsername') {
34
35
  tokenUsername = usernameField.name;
@@ -38,12 +39,12 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
38
39
  user: null
39
40
  };
40
41
  }
41
- const figmaUserInfo = getFigmaUserInfo(headers);
42
+ const figmaUserInfo = getFigmaUserInfo(headers, userInfoCookieName);
42
43
  let responseHeaders;
43
44
  // Clear the cookie after reading
44
45
  if (figmaUserInfo) {
45
46
  responseHeaders = new Headers();
46
- responseHeaders.append('Set-Cookie', 'figma-user-info=; Max-Age=0; Path=/;');
47
+ responseHeaders.append('Set-Cookie', `${userInfoCookieName}=; Max-Age=0; Path=/;`);
47
48
  }
48
49
  let user = null;
49
50
  const depth = typeof collection.auth === 'object' ? collection.auth.depth : undefined;
@@ -262,7 +262,7 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
262
262
  })).toString('base64');
263
263
  const userInfoCookie = generateCookie({
264
264
  ...cookieOptions,
265
- name: 'figma-user-info',
265
+ name: pluginOptions?.userInfoCookieName ?? 'figma-user-info',
266
266
  expires: new Date(Date.now() + 60_000),
267
267
  returnCookieAsObject: false,
268
268
  value: userInfoValue
@@ -144,6 +144,7 @@ export const oAuth2Plugin = (pluginOptions)=>(config)=>{
144
144
  verify: collectionOptions.verify ?? defaultVerify({
145
145
  collection: existingCollection,
146
146
  strategyName,
147
+ userInfoCookieName: pluginOptions?.userInfoCookieName,
147
148
  usernameField
148
149
  })
149
150
  });
@@ -148,6 +148,16 @@ export interface PluginOptions {
148
148
  * @default 'oauth'
149
149
  */
150
150
  strategyName?: string;
151
+ /**
152
+ * The name of the short-lived cookie used to carry Figma user profile
153
+ * info from the login endpoint to `defaultVerify`.
154
+ *
155
+ * Override in local dev when running multiple Payload apps on localhost
156
+ * to avoid cookie collisions (all localhost apps share a cookie jar).
157
+ *
158
+ * @default 'figma-user-info'
159
+ */
160
+ userInfoCookieName?: string;
151
161
  }
152
162
  export type CookieOptions = {
153
163
  domain?: string;
@@ -0,0 +1,8 @@
1
+ import type { TokenStore } from '../auth/token-store.js';
2
+ /**
3
+ * Print a one-shot banner when the plugin is about to use `tokenStore`
4
+ * auth but no valid OAuth credential is present. Keeps the dev loop
5
+ * actionable instead of relying on per-request middleware errors.
6
+ */
7
+ export declare function logMissingCliAuth(tokenStore: TokenStore): void;
8
+ //# sourceMappingURL=auth-preflight.d.ts.map
@@ -0,0 +1,20 @@
1
+ import * as p from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ /**
4
+ * Print a one-shot banner when the plugin is about to use `tokenStore`
5
+ * auth but no valid OAuth credential is present. Keeps the dev loop
6
+ * actionable instead of relying on per-request middleware errors.
7
+ */ export function logMissingCliAuth(tokenStore) {
8
+ if (tokenStore.hasValidOauthToken()) {
9
+ return;
10
+ }
11
+ const lines = [
12
+ 'Not logged in to the Figma CLI.',
13
+ 'Content API requests will fail until you authenticate.',
14
+ '',
15
+ `Run ${pc.cyan('npx @payloadcms/figma login')} to log in, then restart the dev server.`
16
+ ];
17
+ p.note(lines.join('\n'), pc.yellow('Figma authentication required'));
18
+ }
19
+
20
+ //# sourceMappingURL=auth-preflight.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Reason the Content System ID could not be resolved. Drives the
3
+ * banner's copy so the dev sees the specific remediation step.
4
+ */
5
+ export type MissingContentSystemIdReason = {
6
+ environmentName: string;
7
+ kind: 'environment-not-found';
8
+ projectId: string;
9
+ } | {
10
+ kind: 'network';
11
+ message: string;
12
+ } | {
13
+ kind: 'no-config';
14
+ } | {
15
+ kind: 'unauthenticated';
16
+ };
17
+ /**
18
+ * Print a banner explaining why Content System ID resolution failed and
19
+ * what to run next. Called just before `buildFigmaConfig` throws, so the
20
+ * actionable step is visible above the stack trace.
21
+ */
22
+ export declare function logMissingContentSystemId(reason: MissingContentSystemIdReason): void;
23
+ //# sourceMappingURL=bootstrap-preflight.d.ts.map
@@ -0,0 +1,45 @@
1
+ import * as p from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ /**
4
+ * Print a banner explaining why Content System ID resolution failed and
5
+ * what to run next. Called just before `buildFigmaConfig` throws, so the
6
+ * actionable step is visible above the stack trace.
7
+ */ export function logMissingContentSystemId(reason) {
8
+ const lines = buildBody(reason);
9
+ p.note(lines.join('\n'), pc.yellow('Figma project not configured'));
10
+ }
11
+ function buildBody(reason) {
12
+ const loginCmd = pc.cyan('npx @payloadcms/figma login');
13
+ const initCmd = pc.cyan('npx @payloadcms/figma init');
14
+ const initForceCmd = pc.cyan('npx @payloadcms/figma init --force');
15
+ if (reason.kind === 'no-config') {
16
+ return [
17
+ 'No Figma project configuration found.',
18
+ '',
19
+ `Run ${initCmd} to set up this project.`
20
+ ];
21
+ }
22
+ if (reason.kind === 'unauthenticated') {
23
+ return [
24
+ 'Project config found, but you are not logged in.',
25
+ 'Cannot fetch bootstrap data without a valid session.',
26
+ '',
27
+ `Run ${loginCmd}, then restart the dev server.`
28
+ ];
29
+ }
30
+ if (reason.kind === 'network') {
31
+ return [
32
+ 'Could not reach the Figma API to fetch bootstrap data.',
33
+ `Error: ${reason.message}`,
34
+ '',
35
+ 'Check your network, then restart the dev server.'
36
+ ];
37
+ }
38
+ return [
39
+ `Environment ${pc.bold(reason.environmentName)} not found for project ${pc.bold(reason.projectId)}.`,
40
+ '',
41
+ `Run ${initForceCmd} to reconfigure, or set FIGMA_ENVIRONMENT_NAME to a valid environment.`
42
+ ];
43
+ }
44
+
45
+ //# sourceMappingURL=bootstrap-preflight.js.map
@@ -15,7 +15,11 @@ import { createStorageClient } from '../storage-content-api/client.js';
15
15
  import { getGenerateSignedURLHandler } from '../storage-content-api/client-uploads/generateSignedURL.js';
16
16
  import { contentApiStorageAdapter } from '../storage-content-api/index.js';
17
17
  import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
18
+ import { getEnvVarSync } from '../utils/env-management.js';
18
19
  import * as log from '../utils/log.js';
20
+ import { logMissingCliAuth } from './auth-preflight.js';
21
+ import { logMissingContentSystemId } from './bootstrap-preflight.js';
22
+ import { getDevCookieNames } from './dev-cookie-names.js';
19
23
  /**
20
24
  * Fields added to users collection for Figma profile info
21
25
  */ const figmaUserFields = [
@@ -47,20 +51,118 @@ function missingOAuthCredential(name) {
47
51
  }
48
52
  /**
49
53
  * Fetch bootstrap data from the API and cache all environments locally.
50
- * Returns the requested environment's data, or null if resolution fails.
54
+ * Returns the requested environment's data, or a typed failure reason.
51
55
  */ async function resolveAndCacheBootstrap(store, projectId, environmentName) {
56
+ let credential;
57
+ try {
58
+ credential = await getValidCredential(store);
59
+ } catch (error) {
60
+ return {
61
+ ok: false,
62
+ reason: {
63
+ kind: 'network',
64
+ message: messageOf(error)
65
+ }
66
+ };
67
+ }
68
+ if (!credential) {
69
+ return {
70
+ ok: false,
71
+ reason: {
72
+ kind: 'unauthenticated'
73
+ }
74
+ };
75
+ }
52
76
  try {
53
- const credential = await getValidCredential(store);
54
- if (!credential) {
55
- return null;
56
- }
57
77
  const bootstrapInfo = await getBootstrapInfo(credential, projectId);
58
78
  cacheAllEnvironments(store, projectId, bootstrapInfo);
59
- return store.getBootstrapData(projectId, environmentName);
60
79
  } catch (error) {
61
- log.warning(`Failed to resolve bootstrap data: ${error instanceof Error ? error.message : 'Unknown error'}. ` + 'Try running `npx @payloadcms/figma login` to refresh authentication.');
62
- return null;
80
+ return {
81
+ ok: false,
82
+ reason: {
83
+ kind: 'network',
84
+ message: messageOf(error)
85
+ }
86
+ };
63
87
  }
88
+ const data = store.getBootstrapData(projectId, environmentName);
89
+ if (!data) {
90
+ return {
91
+ ok: false,
92
+ reason: {
93
+ environmentName,
94
+ kind: 'environment-not-found',
95
+ projectId
96
+ }
97
+ };
98
+ }
99
+ return {
100
+ data,
101
+ ok: true
102
+ };
103
+ }
104
+ function messageOf(error) {
105
+ return error instanceof Error ? error.message : 'Unknown error';
106
+ }
107
+ /**
108
+ * Creates storage configuration for upload collections
109
+ */ function createStorageConfig(url, contentSystemId) {
110
+ const storageConfig = process.env.FIGMA_CONTENT_API_ACCESS_KEY ? {
111
+ baseUrl: url,
112
+ contentApiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY
113
+ } : process.env.FIGMA_DEV_JWT === 'true' ? {
114
+ auth: {
115
+ mode: 'devJwt'
116
+ },
117
+ baseUrl: url,
118
+ contentSystemId
119
+ } : {
120
+ auth: {
121
+ mode: 'tokenStore',
122
+ tokenStore: getTokenStore()
123
+ },
124
+ baseUrl: url,
125
+ contentSystemId
126
+ };
127
+ const adapter = contentApiStorageAdapter(storageConfig);
128
+ const storageClient = createStorageClient(storageConfig);
129
+ return {
130
+ adapter,
131
+ storageClient
132
+ };
133
+ }
134
+ /**
135
+ * Plugin that applies cloud storage configuration to all upload-enabled collections.
136
+ * Runs at the end of the plugin chain so it sees collections added by user plugins too.
137
+ */ function createStoragePlugin(url, contentSystemId) {
138
+ return (incomingConfig)=>{
139
+ const uploadCollections = (incomingConfig.collections || []).filter((c)=>c.upload);
140
+ if (uploadCollections.length === 0) {
141
+ return incomingConfig;
142
+ }
143
+ const { adapter, storageClient } = createStorageConfig(url, contentSystemId);
144
+ const collectionsMap = uploadCollections.reduce((acc, c)=>{
145
+ acc[c.slug] = {
146
+ adapter,
147
+ disableLocalStorage: true
148
+ };
149
+ return acc;
150
+ }, {});
151
+ initClientUploads({
152
+ clientHandler: '@payloadcms/figma/client#ContentApiClientUploadHandler',
153
+ collections: collectionsMap,
154
+ config: incomingConfig,
155
+ enabled: true,
156
+ serverHandler: getGenerateSignedURLHandler({
157
+ client: storageClient
158
+ }),
159
+ serverHandlerPath: '/content-api-storage-signed-url'
160
+ });
161
+ const storagePlugin = cloudStoragePlugin({
162
+ collections: collectionsMap
163
+ });
164
+ return storagePlugin(incomingConfig);
165
+ };
64
166
  }
65
167
  export async function buildFigmaConfig(config) {
66
168
  const envConfig = getEnvConfig();
@@ -68,16 +170,27 @@ export async function buildFigmaConfig(config) {
68
170
  let { contentSystemId } = config.figma;
69
171
  contentSystemId ??= process.env.FIGMA_CONTENT_API_CONTENT_SYSTEM_ID;
70
172
  let bootstrapData = null;
173
+ let bootstrapReason = null;
71
174
  // Local dev: resolve from project ID + bootstrap cache
175
+ // Use getEnvVarSync fallback because Next.js evaluates config before dotenv populates process.env
72
176
  if (!contentSystemId) {
73
- const projectId = process.env.FIGMA_PROJECT_ID;
74
- const environmentName = process.env.FIGMA_ENVIRONMENT_NAME;
75
- if (projectId && environmentName) {
177
+ const projectId = process.env.FIGMA_PROJECT_ID ?? getEnvVarSync(process.cwd(), 'FIGMA_PROJECT_ID');
178
+ const environmentName = process.env.FIGMA_ENVIRONMENT_NAME ?? getEnvVarSync(process.cwd(), 'FIGMA_ENVIRONMENT_NAME');
179
+ if (!projectId || !environmentName) {
180
+ bootstrapReason = {
181
+ kind: 'no-config'
182
+ };
183
+ } else {
76
184
  const store = getTokenStore();
77
185
  bootstrapData = store.getBootstrapData(projectId, environmentName);
78
186
  // Runtime fallback: fetch from API if not cached (e.g. environment switch without re-init)
79
187
  if (!bootstrapData) {
80
- bootstrapData = await resolveAndCacheBootstrap(store, projectId, environmentName);
188
+ const resolution = await resolveAndCacheBootstrap(store, projectId, environmentName);
189
+ if (resolution.ok) {
190
+ bootstrapData = resolution.data;
191
+ } else {
192
+ bootstrapReason = resolution.reason;
193
+ }
81
194
  }
82
195
  if (bootstrapData) {
83
196
  contentSystemId = bootstrapData.contentSystemId;
@@ -85,10 +198,17 @@ export async function buildFigmaConfig(config) {
85
198
  }
86
199
  }
87
200
  if (!contentSystemId) {
201
+ logMissingContentSystemId(bootstrapReason ?? {
202
+ kind: 'no-config'
203
+ });
88
204
  throw new Error('Content System ID could not be resolved. ' + 'Run `npx @payloadcms/figma init` to set up your project.');
89
205
  }
90
206
  const url = process.env.FIGMA_CONTENT_API_URL || envConfig.contentApiUrl;
91
207
  const isProduction = process.env.NODE_ENV === 'production';
208
+ const usesTokenStoreAuth = !process.env.FIGMA_CONTENT_API_ACCESS_KEY && process.env.FIGMA_DEV_JWT !== 'true';
209
+ if (usesTokenStoreAuth) {
210
+ logMissingCliAuth(getTokenStore());
211
+ }
92
212
  // Determine database adapter based on environment
93
213
  let db;
94
214
  if (config.figma.useContentSystem === false) {
@@ -120,50 +240,6 @@ export async function buildFigmaConfig(config) {
120
240
  url
121
241
  });
122
242
  }
123
- // Build storage plugin if there are upload collections and storage is not disabled
124
- const uploadCollections = (config.collections || []).filter((c)=>c.upload);
125
- let storagePlugin;
126
- if (uploadCollections.length > 0 && config.figma.storage !== false) {
127
- const storageConfig = process.env.FIGMA_CONTENT_API_ACCESS_KEY ? {
128
- baseUrl: url,
129
- contentApiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY
130
- } : process.env.FIGMA_DEV_JWT === 'true' ? {
131
- auth: {
132
- mode: 'devJwt'
133
- },
134
- baseUrl: url,
135
- contentSystemId
136
- } : {
137
- auth: {
138
- mode: 'tokenStore',
139
- tokenStore: getTokenStore()
140
- },
141
- baseUrl: url,
142
- contentSystemId
143
- };
144
- const adapter = contentApiStorageAdapter(storageConfig);
145
- const storageClient = createStorageClient(storageConfig);
146
- const collectionsMap = uploadCollections.reduce((acc, c)=>{
147
- acc[c.slug] = {
148
- adapter,
149
- disableLocalStorage: true
150
- };
151
- return acc;
152
- }, {});
153
- initClientUploads({
154
- clientHandler: '@payloadcms/figma/client#ContentApiClientUploadHandler',
155
- collections: collectionsMap,
156
- config: config,
157
- enabled: true,
158
- serverHandler: getGenerateSignedURLHandler({
159
- client: storageClient
160
- }),
161
- serverHandlerPath: '/content-api-storage-signed-url'
162
- });
163
- storagePlugin = cloudStoragePlugin({
164
- collections: collectionsMap
165
- });
166
- }
167
243
  // Build complete config with Figma platform defaults
168
244
  const configWithFigmaDefaults = {
169
245
  ...config,
@@ -197,8 +273,8 @@ export async function buildFigmaConfig(config) {
197
273
  // Add oauth to plugins if not already present
198
274
  plugins: [
199
275
  ...config.plugins ?? [],
200
- ...storagePlugin ? [
201
- storagePlugin
276
+ ...config.figma.storage !== false ? [
277
+ createStoragePlugin(url, contentSystemId)
202
278
  ] : [],
203
279
  oAuth2Plugin({
204
280
  collections: [
@@ -219,6 +295,7 @@ export async function buildFigmaConfig(config) {
219
295
  usernameField: 'email'
220
296
  }
221
297
  ],
298
+ ...isProduction ? {} : getDevCookieNames(contentSystemId),
222
299
  debug: !!process.env.DEBUG,
223
300
  disabled: false
224
301
  }),
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Namespace OAuth cookies per-app in local dev so multiple Payload apps
3
+ * running on localhost (shared cookie jar across ports) don't clobber each
4
+ * other's sessions on logout. Uses projectId so the cookie stays stable when
5
+ * switching environments in the same checkout; falls back to contentSystemId
6
+ * when projectId isn't in `.env`.
7
+ *
8
+ * In production, cookies are isolated by unique hostnames and this is unused.
9
+ */
10
+ export declare function getDevCookieNames(contentSystemId: string): {
11
+ cookieName: string;
12
+ userInfoCookieName: string;
13
+ };
14
+ //# sourceMappingURL=dev-cookie-names.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { getEnvVarSync } from '../utils/env-management.js';
2
+ /**
3
+ * Namespace OAuth cookies per-app in local dev so multiple Payload apps
4
+ * running on localhost (shared cookie jar across ports) don't clobber each
5
+ * other's sessions on logout. Uses projectId so the cookie stays stable when
6
+ * switching environments in the same checkout; falls back to contentSystemId
7
+ * when projectId isn't in `.env`.
8
+ *
9
+ * In production, cookies are isolated by unique hostnames and this is unused.
10
+ */ export function getDevCookieNames(contentSystemId) {
11
+ const projectId = process.env.FIGMA_PROJECT_ID ?? getEnvVarSync(process.cwd(), 'FIGMA_PROJECT_ID') ?? contentSystemId;
12
+ const suffix = projectId.slice(0, 8);
13
+ return {
14
+ cookieName: `figma-${suffix}-oauth-token`,
15
+ userInfoCookieName: `figma-${suffix}-user-info`
16
+ };
17
+ }
18
+
19
+ //# sourceMappingURL=dev-cookie-names.js.map
@@ -18,7 +18,10 @@ function createStorageAuthMiddleware(config) {
18
18
  if (auth.mode === 'apiKey') {
19
19
  request.headers.set('X-Api-Key', auth.apiKey);
20
20
  } else if (auth.mode === 'tokenStore') {
21
- const token = await getValidProjectToken(auth.tokenStore, contentSystemId);
21
+ const token = await getValidProjectToken({
22
+ tenantId: contentSystemId,
23
+ tokenStore: auth.tokenStore
24
+ });
22
25
  if (!token) {
23
26
  throw new Error('Authentication required. Run `npx @payloadcms/figma login` to authenticate.');
24
27
  }
@@ -14,6 +14,7 @@ export function helpMessage() {
14
14
  ${pc.cyan('logout')} Clear all stored tokens
15
15
  ${pc.cyan('list-tokens')} Show stored token information
16
16
  ${pc.cyan('init')} Initialize a Figma CMS project
17
+ ${pc.cyan('bootstrap')} Print bootstrap info (tenant IDs, OAuth creds) for a project
17
18
  ${pc.cyan('debug')} Show debug info for troubleshooting
18
19
  ${pc.cyan('env')} Switch active environment
19
20
  ${pc.cyan('deploy')} Deploy your project to Figma
@@ -39,6 +40,12 @@ export function helpMessage() {
39
40
  ${pc.dim('--name, -n <name>')} Set project directory name (skips prompt)
40
41
  ${pc.dim('--force')} Force reconfiguration of existing project
41
42
 
43
+ ${pc.bold('BOOTSTRAP COMMAND')}
44
+
45
+ ${pc.cyan('@payloadcms/figma bootstrap --id <cms-resource-id>')} Print bootstrap info
46
+ ${pc.dim('--env <environment>')} Filter to a single environment
47
+ ${pc.dim('--json')} Output JSON instead of styled note
48
+
42
49
  ${pc.bold('ENV COMMAND')}
43
50
 
44
51
  ${pc.cyan('@payloadcms/figma env')} List and switch environments