@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
package/dist/cli.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import arg from 'arg';
3
3
  import pc from 'picocolors';
4
+ import { bootstrapCommand } from './commands/bootstrap.js';
4
5
  import { buildLambdaZipCommand } from './commands/build-lambda-zip.js';
5
6
  import { debugCommand } from './commands/debug.js';
6
7
  import { deployCommand } from './commands/deploy.js';
8
+ import { dumpTokensCommand } from './commands/dump-tokens.js';
7
9
  import { envCommand } from './commands/env.js';
8
10
  import { initCommand } from './commands/init.js';
9
11
  import { listTokensCommand } from './commands/list-tokens.js';
@@ -41,6 +43,7 @@ class Main {
41
43
  '--help': Boolean,
42
44
  '--id': String,
43
45
  '--infra-env': String,
46
+ '--json': Boolean,
44
47
  '--name': String,
45
48
  '--skip-auth': Boolean,
46
49
  '--skip-build': Boolean,
@@ -101,11 +104,23 @@ class Main {
101
104
  await debugCommand();
102
105
  process.exit(0);
103
106
  }
107
+ // dump-tokens outputs plain text
108
+ if (subcommand === 'dump-tokens') {
109
+ await dumpTokensCommand();
110
+ process.exit(0);
111
+ }
104
112
  // eslint-disable-next-line no-console
105
113
  console.log('\n');
106
114
  p.intro(pc.bgCyan(pc.black(' @payloadcms/figma ')));
107
115
  // Route to appropriate command handler
108
116
  switch(subcommand){
117
+ case 'bootstrap':
118
+ await bootstrapCommand({
119
+ id: this.args['--id'],
120
+ env: this.args['--env'],
121
+ json: this.args['--json']
122
+ });
123
+ break;
109
124
  case 'build-lambda-zip':
110
125
  await buildLambdaZipCommand();
111
126
  break;
@@ -0,0 +1,18 @@
1
+ export type BootstrapCommandOptions = {
2
+ /** Filter to a single environment (e.g. "production", "staging"). */
3
+ env?: string;
4
+ /** CMS Resource ID (FIGMA_PROJECT_ID, e.g. cms_…). */
5
+ id?: string;
6
+ /** Emit plain JSON instead of a styled note (handy for piping into jq). */
7
+ json?: boolean;
8
+ };
9
+ /**
10
+ * Fetch and display bootstrap info for a CMS resource.
11
+ *
12
+ * Calls the Control Plane bootstrap endpoint with the current OAuth credential
13
+ * and prints the resulting environments (`contentSystemId`, `tenantInstanceId`)
14
+ * plus OAuth client credentials. Useful for grabbing a tenant ID without having
15
+ * to `init` a project.
16
+ */
17
+ export declare function bootstrapCommand(options?: BootstrapCommandOptions): Promise<void>;
18
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1,90 @@
1
+ import * as p from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import { ControlPlaneError, getBootstrapInfo } from '../api/control-plane.js';
4
+ import { getValidCredential } from '../auth/oauth-flow.js';
5
+ import { getTokenStore } from '../auth/token-store.js';
6
+ import { getInfraEnvironment } from '../constants.js';
7
+ import { isDebug } from '../utils/is-debug.js';
8
+ import { maskToken } from '../utils/token-display.js';
9
+ /**
10
+ * Fetch and display bootstrap info for a CMS resource.
11
+ *
12
+ * Calls the Control Plane bootstrap endpoint with the current OAuth credential
13
+ * and prints the resulting environments (`contentSystemId`, `tenantInstanceId`)
14
+ * plus OAuth client credentials. Useful for grabbing a tenant ID without having
15
+ * to `init` a project.
16
+ */ export async function bootstrapCommand(options = {}) {
17
+ if (!options.id) {
18
+ p.log.error(pc.red('--id <project-id> is required'));
19
+ p.note('The CMS Resource ID (FIGMA_PROJECT_ID), e.g. cms_abc123', 'Tip');
20
+ process.exit(1);
21
+ }
22
+ const tokenStore = getTokenStore(getInfraEnvironment());
23
+ let credential;
24
+ try {
25
+ credential = await getValidCredential(tokenStore);
26
+ } catch (error) {
27
+ p.log.error(pc.red(`Failed to read stored credentials: ${error instanceof Error ? error.message : 'Unknown error'}`));
28
+ p.note(`Run ${pc.cyan(`@payloadcms/figma logout --infra-env ${getInfraEnvironment()} -y`)} ` + `then ${pc.cyan(`@payloadcms/figma login --infra-env ${getInfraEnvironment()}`)} and retry.`, 'Re-authenticate');
29
+ if (isDebug() && error instanceof Error) {
30
+ // eslint-disable-next-line no-console
31
+ console.error(error);
32
+ }
33
+ process.exit(1);
34
+ }
35
+ if (!credential) {
36
+ p.log.error(pc.red('Not authenticated.'));
37
+ p.note(`Run ${pc.cyan('@payloadcms/figma login')} first`, 'Tip');
38
+ process.exit(1);
39
+ }
40
+ let info;
41
+ try {
42
+ info = await getBootstrapInfo(credential, options.id, options.env);
43
+ } catch (error) {
44
+ if (error instanceof ControlPlaneError) {
45
+ p.log.error(pc.red(`Bootstrap fetch failed: ${error.statusCode} ${error.message}`));
46
+ if (error.statusCode === 401) {
47
+ p.note(`Stored credentials for infra env ${pc.bold(getInfraEnvironment())} were rejected.\n` + `Run ${pc.cyan(`@payloadcms/figma logout --infra-env ${getInfraEnvironment()} -y`)} ` + `then ${pc.cyan(`@payloadcms/figma login --infra-env ${getInfraEnvironment()}`)} and retry.`, 'Re-authenticate');
48
+ } else if (error.statusCode === 404) {
49
+ p.note(`Resource ${pc.bold(options.id)} was not found in infra env ${pc.bold(getInfraEnvironment())}.\n` + `Try ${pc.cyan('--infra-env staging')} (or production) to target a different infra.`, 'Wrong infra environment?');
50
+ }
51
+ } else if (error instanceof Error) {
52
+ const causeMessage = error.cause instanceof Error ? `: ${error.cause.message}` : '';
53
+ p.log.error(pc.red(`${error.name}: ${error.message}${causeMessage}`));
54
+ if (isDebug()) {
55
+ // eslint-disable-next-line no-console
56
+ console.error(error);
57
+ }
58
+ } else {
59
+ p.log.error(pc.red('Unknown error'));
60
+ }
61
+ process.exit(1);
62
+ }
63
+ if (options.json) {
64
+ const showSecretsInJson = isDebug();
65
+ const payload = showSecretsInJson ? info : {
66
+ ...info,
67
+ oauthCredentials: {
68
+ ...info.oauthCredentials,
69
+ oauthClientSecret: maskToken(info.oauthCredentials.oauthClientSecret)
70
+ }
71
+ };
72
+ // eslint-disable-next-line no-console
73
+ console.log(JSON.stringify(payload, null, 2));
74
+ return;
75
+ }
76
+ const showSecret = isDebug();
77
+ const lines = [
78
+ `Project ID: ${options.id}`,
79
+ `OAuth Client ID: ${info.oauthCredentials.oauthClientId}`,
80
+ `OAuth Client Secret: ${showSecret ? info.oauthCredentials.oauthClientSecret : maskToken(info.oauthCredentials.oauthClientSecret)}`,
81
+ '',
82
+ 'Environments:'
83
+ ];
84
+ for (const env of info.environments){
85
+ lines.push(` ${pc.bold(env.name)}`, ` Tenant ID (contentSystemId): ${env.contentSystemId}`, ` Tenant Instance ID: ${env.tenantInstanceId}`);
86
+ }
87
+ p.note(lines.join('\n'), 'Bootstrap Info');
88
+ }
89
+
90
+ //# sourceMappingURL=bootstrap.js.map
@@ -36,14 +36,14 @@ function appendSystemSection(lines, env) {
36
36
  lines.push('');
37
37
  }
38
38
  function appendAuthSection(lines, tokenStore, cwd) {
39
- const tokens = tokenStore.getTokens();
39
+ const tokens = tokenStore.getOauthInfo();
40
40
  lines.push('Auth:');
41
41
  if (!tokens) {
42
42
  lines.push(' Status: Not authenticated');
43
43
  lines.push('');
44
44
  return;
45
45
  }
46
- const isValid = tokenStore.hasValidTokens();
46
+ const isValid = tokenStore.hasValidOauthToken();
47
47
  lines.push(` Status: ${isValid ? 'Authenticated' : 'Token expired'}`);
48
48
  const email = getEmailFromClaims(tokenStore, cwd);
49
49
  if (email) {
@@ -62,7 +62,7 @@ function appendTokenStoreDiagnostics(lines, tokenStore) {
62
62
  lines.push(` File: ${filePath}`);
63
63
  lines.push(` File Exists: ${fileExists}`);
64
64
  lines.push(` File Size: ${fileSize}B`);
65
- const canRead = tokenStore.getTokens() !== null || tokenStore.getRefreshToken() !== null;
65
+ const canRead = tokenStore.getOauthInfo() !== null || tokenStore.getCurrentRefreshToken() !== null;
66
66
  lines.push(` Decryption: ${fileExists ? canRead || fileSize === 0 ? 'OK' : 'FAILED' : 'N/A'}`);
67
67
  lines.push(` Key Hash: ${getEncryptionKeyHash()}`);
68
68
  lines.push('');
@@ -85,7 +85,12 @@ function appendProjectSection(lines, tokenStore, cwd) {
85
85
  lines.push(` Content System ID: ${bootstrapData.contentSystemId}`);
86
86
  lines.push(` Tenant Instance ID: ${bootstrapData.tenantInstanceId}`);
87
87
  lines.push(` OAuth Client ID: ${maskToken(bootstrapData.oauthClientId)}`);
88
- const projectToken = tokenStore.getProjectToken(bootstrapData.contentSystemId);
88
+ const projectToken = tokenStore.getProjectToken({
89
+ projectInfo: {
90
+ environmentName,
91
+ projectId
92
+ }
93
+ });
89
94
  if (projectToken) {
90
95
  lines.push(` Project Token: ${formatExpiryTime(projectToken.expiresAt)}`);
91
96
  }
@@ -103,25 +108,24 @@ async function appendPackagesSection(lines) {
103
108
  lines.push(` payload: ${payloadVersion}`);
104
109
  }
105
110
  /**
106
- * Try resolving email from bootstrap data, then iterate all stored project tokens.
111
+ * Resolve email from the current project + environment scoped token when available.
107
112
  */ function getEmailFromClaims(tokenStore, cwd) {
108
113
  const projectId = getEnvVarSync(cwd, 'FIGMA_PROJECT_ID');
109
114
  const environmentName = getEnvVarSync(cwd, 'FIGMA_ENVIRONMENT_NAME');
110
115
  if (projectId && environmentName) {
111
116
  const bootstrapData = tokenStore.getBootstrapData(projectId, environmentName);
112
117
  if (bootstrapData) {
113
- const email = tokenStore.getProjectTokenClaims(bootstrapData.contentSystemId)?.email;
118
+ const email = tokenStore.getProjectToken({
119
+ projectInfo: {
120
+ environmentName,
121
+ projectId
122
+ }
123
+ })?.claims?.email;
114
124
  if (email) {
115
125
  return email;
116
126
  }
117
127
  }
118
128
  }
119
- for (const id of tokenStore.getAllProjectTokenTenantIds()){
120
- const email = tokenStore.getProjectTokenClaims(id)?.email;
121
- if (email) {
122
- return email;
123
- }
124
- }
125
129
  return null;
126
130
  }
127
131
  function getPayloadVersion() {
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Handle the `@payloadcms/figma dump-tokens` command
3
+ *
4
+ * Exports decrypted versions of encrypted token store files in
5
+ * `oauth/` and `projects/` subdirectories (new structure) plus any legacy
6
+ * per-env directories from older package versions. Creates
7
+ * `.decrypted.json` files next to each encrypted `.json` file.
8
+ *
9
+ * Skips the `payloadcms-figma-crypto.json` salt file at the root.
10
+ */
11
+ export declare function dumpTokensCommand(): Promise<void>;
12
+ //# sourceMappingURL=dump-tokens.d.ts.map
@@ -0,0 +1,69 @@
1
+ import * as p from '@clack/prompts';
2
+ import envPaths from 'env-paths';
3
+ import fsSync from 'node:fs';
4
+ import path from 'node:path';
5
+ import pc from 'picocolors';
6
+ import { deriveEncryptionKey, exportDecryptedStoreFiles } from '../auth/crypto-utils.js';
7
+ /**
8
+ * Handle the `@payloadcms/figma dump-tokens` command
9
+ *
10
+ * Exports decrypted versions of encrypted token store files in
11
+ * `oauth/` and `projects/` subdirectories (new structure) plus any legacy
12
+ * per-env directories from older package versions. Creates
13
+ * `.decrypted.json` files next to each encrypted `.json` file.
14
+ *
15
+ * Skips the `payloadcms-figma-crypto.json` salt file at the root.
16
+ */ export async function dumpTokensCommand() {
17
+ const encryptionKey = deriveEncryptionKey();
18
+ const newRoot = envPaths('payloadcms-figma').config;
19
+ const stagingLegacyRoot = envPaths('payloadcms-figma-staging').config;
20
+ const candidateDirs = [
21
+ path.join(newRoot, 'oauth'),
22
+ path.join(newRoot, 'projects'),
23
+ stagingLegacyRoot
24
+ ];
25
+ const targetDirs = Array.from(new Set(candidateDirs)).filter((dir)=>fsSync.existsSync(dir));
26
+ if (targetDirs.length === 0) {
27
+ // eslint-disable-next-line no-console
28
+ console.log('No token store directories found.');
29
+ return;
30
+ }
31
+ // eslint-disable-next-line no-console
32
+ console.log(pc.yellow('⚠ dump-tokens writes DECRYPTED token files to disk.'));
33
+ // eslint-disable-next-line no-console
34
+ console.log('Target directories:');
35
+ for (const dir of targetDirs){
36
+ // eslint-disable-next-line no-console
37
+ console.log(` ${dir}`);
38
+ }
39
+ // eslint-disable-next-line no-console
40
+ console.log('');
41
+ const confirmed = await p.confirm({
42
+ initialValue: false,
43
+ message: 'Continue? Remember to delete the decrypted files after use.'
44
+ });
45
+ if (p.isCancel(confirmed) || !confirmed) {
46
+ // eslint-disable-next-line no-console
47
+ console.log('Cancelled.');
48
+ return;
49
+ }
50
+ const decryptedFiles = [];
51
+ for (const dir of targetDirs){
52
+ decryptedFiles.push(...exportDecryptedStoreFiles(dir, encryptionKey));
53
+ }
54
+ if (decryptedFiles.length === 0) {
55
+ // eslint-disable-next-line no-console
56
+ console.log('No encrypted files could be decrypted with the current machine key.');
57
+ return;
58
+ }
59
+ // eslint-disable-next-line no-console
60
+ console.log(`\nCreated ${decryptedFiles.length} decrypted file(s):`);
61
+ for (const file of decryptedFiles){
62
+ // eslint-disable-next-line no-console
63
+ console.log(` ${file}`);
64
+ }
65
+ // eslint-disable-next-line no-console
66
+ console.log('\nNote: These files contain sensitive tokens. Delete after use.');
67
+ }
68
+
69
+ //# sourceMappingURL=dump-tokens.js.map
@@ -29,12 +29,16 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
29
29
  * @param tokenStore - Token store instance
30
30
  * @param contentSystemId - Content System ID to generate token for
31
31
  * @param spinner - Clack spinner instance for status updates (only used in debug mode)
32
- */ async function generateProjectTokenWithFeedback(tokenStore, contentSystemId, spinner) {
32
+ */ async function generateProjectTokenWithFeedback(tokenStore, contentSystemId, spinner, projectInfo) {
33
33
  if (isDebug()) {
34
34
  spinner.start('Generating project token...');
35
35
  }
36
36
  try {
37
- const projectToken = await getValidProjectToken(tokenStore, contentSystemId);
37
+ const projectToken = await getValidProjectToken({
38
+ projectInfo,
39
+ tenantId: contentSystemId,
40
+ tokenStore
41
+ });
38
42
  if (isDebug()) {
39
43
  if (projectToken) {
40
44
  spinner.stop(pc.green('✓ Project token generated'));
@@ -78,7 +82,7 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
78
82
  p.log.warn(pc.yellow('Skipping authentication (--skip-auth mode)'));
79
83
  } else {
80
84
  // Check for valid cached tokens first (no API call needed)
81
- if (!tokenStore.hasValidTokens()) {
85
+ if (!tokenStore.hasValidOauthToken()) {
82
86
  // Need to refresh or authenticate - show spinner for API call
83
87
  s.start('Checking authentication...');
84
88
  const credential = await tryGetCredential(tokenStore);
@@ -221,6 +225,18 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
221
225
  p.note('Run this command in a directory without an existing project or in an existing Payload project.', 'Action Required');
222
226
  process.exit(1);
223
227
  }
228
+ // npm leaves node_modules in a transient state after a sequence of
229
+ // add/remove calls; without a final `install` the subprocess that runs
230
+ // generate:importmap silently misses @payloadcms/figma subpath components.
231
+ s.start('Reconciling dependencies...');
232
+ try {
233
+ await installDependencies(process.cwd(), packageManager);
234
+ s.stop(pc.green('✓ Dependencies reconciled'));
235
+ } catch (error) {
236
+ s.stop(pc.red('✗ Failed to reconcile dependencies before generate:importmap'));
237
+ log.error(error instanceof Error ? error.message : 'Unknown error');
238
+ process.exit(1);
239
+ }
224
240
  // Generate import map to prevent errors on first dev run
225
241
  const importMapResult = await runScript(process.cwd(), 'generate:importmap', packageManager);
226
242
  if (!importMapResult) {
@@ -236,7 +252,10 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
236
252
  ]);
237
253
  // Generate project token (unless skipping auth)
238
254
  if (!options.skipAuth && resolvedEnv) {
239
- await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s);
255
+ await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s, {
256
+ environmentName: resolvedEnv.name,
257
+ projectId: cmsResourceId
258
+ });
240
259
  }
241
260
  // Success message for existing project
242
261
  p.outro(pc.green('✓ Project initialized successfully!'));
@@ -335,6 +354,18 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
335
354
  } catch (error) {
336
355
  p.log.warn(pc.yellow(`⚠ Failed to update .env file: ${error instanceof Error ? error.message : 'Unknown error'}`));
337
356
  }
357
+ // npm leaves node_modules in a transient state after a sequence of
358
+ // add/remove calls; without a final `install` the subprocess that runs
359
+ // generate:importmap silently misses @payloadcms/figma subpath components.
360
+ s.start('Reconciling dependencies...');
361
+ try {
362
+ await installDependencies(fullPath, packageManager);
363
+ s.stop(pc.green('✓ Dependencies reconciled'));
364
+ } catch (error) {
365
+ s.stop(pc.red('✗ Failed to reconcile dependencies before generate:importmap'));
366
+ log.error(error instanceof Error ? error.message : 'Unknown error');
367
+ process.exit(1);
368
+ }
338
369
  // Generate import map to prevent errors on first dev run
339
370
  const importMapResult = await runScript(fullPath, 'generate:importmap', packageManager);
340
371
  if (!importMapResult) {
@@ -342,7 +373,10 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
342
373
  }
343
374
  // Generate project token (unless skipping auth)
344
375
  if (!options.skipAuth && resolvedEnv) {
345
- await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s);
376
+ await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s, {
377
+ environmentName: resolvedEnv.name,
378
+ projectId: cmsResourceId
379
+ });
346
380
  }
347
381
  // Initialize git repository (after all files including lock file are ready)
348
382
  initializeGitRepo(fullPath);
@@ -26,13 +26,13 @@ const ALL_ENVIRONMENTS = [
26
26
  }
27
27
  function showTokenDetails(env) {
28
28
  const tokenStore = getTokenStore(env);
29
- const tokens = tokenStore.getTokens();
29
+ const tokens = tokenStore.getOauthInfo();
30
30
  if (!tokens) {
31
31
  p.log.warn(pc.yellow(`No tokens found${formatEnvSuffix(env, ' for ')}. Not authenticated.`));
32
32
  p.note(`Run ${pc.cyan('@payloadcms/figma login')} to authenticate`, 'Tip');
33
33
  return;
34
34
  }
35
- const isValid = tokenStore.hasValidTokens();
35
+ const isValid = tokenStore.hasValidOauthToken();
36
36
  const status = isValid ? pc.green('✓ Valid') : pc.red('✗ Expired');
37
37
  p.log.step(pc.bold(`Token Status${formatEnvSuffix(env, ' (', ')')}`));
38
38
  // Build OAuth token info display
@@ -22,7 +22,7 @@ import * as log from '../utils/log.js';
22
22
  try {
23
23
  const existingCredential = await getValidCredential(tokenStore);
24
24
  if (existingCredential) {
25
- const tokens = tokenStore.getTokens();
25
+ const tokens = tokenStore.getOauthInfo();
26
26
  p.log.warn(pc.yellow(`Already logged in${formatEnvSuffix(env)}`));
27
27
  if (tokens?.userId) {
28
28
  log.info(`User ID: ${tokens.userId}`);
@@ -2,6 +2,7 @@ import * as p from '@clack/prompts';
2
2
  import pc from 'picocolors';
3
3
  import { getTokenStore } from '../auth/token-store.js';
4
4
  import { getInfraEnvironment } from '../constants.js';
5
+ import { getEnvVarSync } from '../utils/env-management.js';
5
6
  import { formatEnvSuffix } from '../utils/format-env-suffix.js';
6
7
  import * as log from '../utils/log.js';
7
8
  const ALL_ENVIRONMENTS = [
@@ -23,7 +24,7 @@ const ALL_ENVIRONMENTS = [
23
24
  }
24
25
  async function logoutSingleEnvironment(env) {
25
26
  const tokenStore = getTokenStore(env);
26
- const hasTokens = tokenStore.getTokens() !== null;
27
+ const hasTokens = tokenStore.getOauthInfo() !== null;
27
28
  if (!hasTokens) {
28
29
  p.log.warn(pc.yellow(`No tokens found${formatEnvSuffix(env, ' for ')}. Already logged out.`));
29
30
  return;
@@ -40,7 +41,7 @@ async function logoutSingleEnvironment(env) {
40
41
  s.start('Clearing tokens...');
41
42
  try {
42
43
  tokenStore.clearTokens();
43
- tokenStore.clearAllProjectTokens();
44
+ clearCurrentProjectToken(tokenStore, env);
44
45
  s.stop(pc.green(`✓ Logged out${formatEnvSuffix(env, ' from ')}`));
45
46
  } catch (error) {
46
47
  s.stop(pc.red('✗ Failed to clear tokens'));
@@ -52,7 +53,7 @@ async function logoutAllEnvironments() {
52
53
  // Check which environments have tokens
53
54
  const envsWithTokens = ALL_ENVIRONMENTS.filter((env)=>{
54
55
  const store = getTokenStore(env);
55
- return store.getTokens() !== null;
56
+ return store.getOauthInfo() !== null;
56
57
  });
57
58
  if (envsWithTokens.length === 0) {
58
59
  p.log.warn(pc.yellow('No tokens found in any environment. Already logged out.'));
@@ -73,7 +74,7 @@ async function logoutAllEnvironments() {
73
74
  for (const env of envsWithTokens){
74
75
  const store = getTokenStore(env);
75
76
  store.clearTokens();
76
- store.clearAllProjectTokens();
77
+ clearCurrentProjectToken(store, env);
77
78
  }
78
79
  s.stop(pc.green('✓ Logged out from all environments'));
79
80
  } catch (error) {
@@ -82,5 +83,23 @@ async function logoutAllEnvironments() {
82
83
  process.exit(1);
83
84
  }
84
85
  }
86
+ function clearCurrentProjectToken(store, env) {
87
+ const projectId = getEnvVarSync(process.cwd(), 'FIGMA_PROJECT_ID');
88
+ if (!projectId) {
89
+ return;
90
+ }
91
+ const bootstrapData = store.getBootstrapData(projectId, env);
92
+ if (!bootstrapData?.contentSystemId) {
93
+ return;
94
+ }
95
+ const projectInfo = {
96
+ environmentName: env,
97
+ projectId
98
+ };
99
+ // Clear scoped token for this tenant.
100
+ store.clearProjectToken({
101
+ projectInfo
102
+ });
103
+ }
85
104
 
86
105
  //# sourceMappingURL=logout.js.map
@@ -673,41 +673,6 @@ async function upsert(args) {
673
673
  payload: this.payload
674
674
  });
675
675
  }
676
- // Mirrors MAX_DOCUMENT_UPDATES in the content API's updateDocuments.ts.
677
- // updateJobs may be called with larger limits (e.g. 150), so we batch sequentially.
678
- const CONTENT_API_MAX_UPDATES = 20;
679
- // Issues a single update request against payload-jobs and returns the unwrapped docs.
680
- async function updateJobsBatch({ batchSize, docData, meta, sortClause, whereQuery }) {
681
- const { data: response, error } = await this.client.POST('/api/v0/documents:update', {
682
- body: {
683
- collection: 'payload-jobs',
684
- contentSystemId: this.contentSystemId,
685
- createOnMissing: false,
686
- doc: docData,
687
- ...batchSize != null && {
688
- limit: batchSize
689
- },
690
- returning: {},
691
- sort: sortClause,
692
- where: whereQuery,
693
- ...meta
694
- }
695
- });
696
- if (error) {
697
- throw new Error(`Content API updateJobs error: ${JSON.stringify(error)}`);
698
- }
699
- if (!response) {
700
- throw new Error('No response from updateJobs');
701
- }
702
- if (!response.result || !('data' in response.result)) {
703
- return [];
704
- }
705
- return response.result.data.map((doc)=>unwrapDocument({
706
- collectionSlug: 'payload-jobs',
707
- doc,
708
- payload: this.payload
709
- }));
710
- }
711
676
  async function updateJobs(args) {
712
677
  const { id, limit, returning, sort, where } = args;
713
678
  if (id == null && where == null) {
@@ -737,49 +702,38 @@ async function updateJobs(args) {
737
702
  locale: undefined,
738
703
  where: whereClause
739
704
  });
740
- // When limit exceeds the content API's per-request cap, batch sequentially.
741
- const needsBatching = limit != null && limit > CONTENT_API_MAX_UPDATES;
742
- if (!needsBatching) {
743
- const docs = await updateJobsBatch.call(this, {
744
- batchSize: limit,
745
- docData,
746
- meta,
747
- sortClause,
748
- whereQuery
749
- });
750
- if (returning === false) {
751
- return null;
752
- }
753
- return docs;
754
- }
755
- // Batched path: issue sequential requests of ≤ CONTENT_API_MAX_UPDATES each.
756
- // This relies on the update itself causing matched jobs to no longer satisfy the where
757
- // clause on subsequent batches (e.g. setting processing: true removes them from a
758
- // "processing: false" query). If a future updateJobs call in Payload core updates jobs
759
- // in a way that does NOT change their match status, the same jobs could be updated
760
- // multiple times across batches.
761
- const allDocs = [];
762
- let remaining = limit;
763
- while(remaining > 0){
764
- const batchSize = Math.min(remaining, CONTENT_API_MAX_UPDATES);
765
- const batchDocs = await updateJobsBatch.call(this, {
766
- batchSize,
767
- docData,
768
- meta,
769
- sortClause,
770
- whereQuery
771
- });
772
- allDocs.push(...batchDocs);
773
- remaining -= batchSize;
774
- // Stop early when this batch returned fewer docs than requested — no more matching docs.
775
- if (batchDocs.length < batchSize) {
776
- break;
705
+ const { data: response, error } = await this.client.POST('/api/v0/documents:update', {
706
+ body: {
707
+ collection: 'payload-jobs',
708
+ contentSystemId: this.contentSystemId,
709
+ createOnMissing: false,
710
+ doc: docData,
711
+ ...limit != null && {
712
+ limit
713
+ },
714
+ returning: {},
715
+ sort: sortClause,
716
+ where: whereQuery,
717
+ ...meta
777
718
  }
719
+ });
720
+ if (error) {
721
+ throw new Error(`Content API updateJobs error: ${JSON.stringify(error)}`);
722
+ }
723
+ if (!response) {
724
+ throw new Error('No response from updateJobs');
778
725
  }
779
726
  if (returning === false) {
780
727
  return null;
781
728
  }
782
- return allDocs;
729
+ if (!response.result || !('data' in response.result)) {
730
+ return [];
731
+ }
732
+ return response.result.data.map((doc)=>unwrapDocument({
733
+ collectionSlug: 'payload-jobs',
734
+ doc,
735
+ payload: this.payload
736
+ }));
783
737
  }
784
738
  function createGlobal(args) {
785
739
  // Globals are singletons identified by their slug. Use upsert so concurrent calls
@@ -25,7 +25,10 @@ export function createAuthMiddleware(opts) {
25
25
  if (opts.auth.mode === 'apiKey') {
26
26
  request.headers.set('X-Api-Key', opts.auth.apiKey);
27
27
  } else if (opts.auth.mode === 'tokenStore') {
28
- const token = await getValidProjectToken(opts.auth.tokenStore, opts.contentSystemId);
28
+ const token = await getValidProjectToken({
29
+ tenantId: opts.contentSystemId,
30
+ tokenStore: opts.auth.tokenStore
31
+ });
29
32
  if (!token) {
30
33
  throw new Error('Authentication required. Run `npx @payloadcms/figma login` to authenticate.');
31
34
  }
@@ -1,10 +1,12 @@
1
1
  import type { CollectionConfig, EmailField, TextField } from 'payload';
2
2
  import type { VerifyFunction } from './types.js';
3
+ export declare const DEFAULT_USER_INFO_COOKIE_NAME = "figma-user-info";
3
4
  export declare const defaultScope: string[];
4
5
  export declare const defaultUsernameField: TextField;
5
- export declare const defaultVerify: ({ collection, strategyName, usernameField, }: {
6
+ export declare const defaultVerify: ({ collection, strategyName, userInfoCookieName, usernameField, }: {
6
7
  collection: CollectionConfig;
7
8
  strategyName: string;
9
+ userInfoCookieName?: string;
8
10
  usernameField: EmailField | TextField;
9
11
  }) => VerifyFunction;
10
12
  //# sourceMappingURL=defaults.d.ts.map