@payloadcms/figma 0.0.1-alpha.52 → 0.0.1-alpha.54

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/cli.js CHANGED
@@ -9,8 +9,8 @@ import { initCommand } from './commands/init.js';
9
9
  import { listTokensCommand } from './commands/list-tokens.js';
10
10
  import { loginCommand } from './commands/login.js';
11
11
  import { logoutCommand } from './commands/logout.js';
12
+ import { upgradeCommand } from './commands/upgrade.js';
12
13
  import { setInfraEnvironment } from './constants.js';
13
- import { handleUpgrade } from './utils/handle-upgrade.js';
14
14
  import { helpMessage } from './utils/messages.js';
15
15
  /**
16
16
  * Entrypoint for bin/cli.js
@@ -31,6 +31,7 @@ class Main {
31
31
  this.args = arg({
32
32
  '--all': Boolean,
33
33
  '--debug': Boolean,
34
+ '--dry-run': Boolean,
34
35
  '--env': String,
35
36
  '--force': Boolean,
36
37
  '--help': Boolean,
@@ -86,15 +87,6 @@ class Main {
86
87
  helpMessage();
87
88
  process.exit(0);
88
89
  }
89
- // Check for upgrade migrations
90
- try {
91
- const upgradeChanges = await handleUpgrade(process.cwd());
92
- if (upgradeChanges.length > 0) {
93
- p.log.info(`Migrated project: ${upgradeChanges.join(', ')}`);
94
- }
95
- } catch {
96
- // Never block CLI execution due to upgrade check
97
- }
98
90
  // Debug command outputs plain text for copy-paste — skip styled intro
99
91
  if (subcommand === 'debug') {
100
92
  await debugCommand();
@@ -147,6 +139,11 @@ class Main {
147
139
  debug: this.args['--debug']
148
140
  });
149
141
  break;
142
+ case 'upgrade':
143
+ await upgradeCommand({
144
+ isDryRun: this.args['--dry-run']
145
+ });
146
+ break;
150
147
  default:
151
148
  p.log.error(pc.red(`Unknown command: ${subcommand}`));
152
149
  p.note('Use --help to see available commands', 'Tip');
@@ -0,0 +1,5 @@
1
+ export type UpgradeCommandOptions = {
2
+ isDryRun?: boolean;
3
+ };
4
+ export declare function upgradeCommand(options?: UpgradeCommandOptions): Promise<void>;
5
+ //# sourceMappingURL=upgrade.d.ts.map
@@ -0,0 +1,331 @@
1
+ import * as p from '@clack/prompts';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import pc from 'picocolors';
5
+ import { Project } from 'ts-morph';
6
+ import { fileURLToPath } from 'url';
7
+ import { getBootstrapInfo, getCmsResourceId } from '../api/control-plane.js';
8
+ import { getValidAccessToken } from '../auth/oauth-flow.js';
9
+ import { getTokenStore } from '../auth/token-store.js';
10
+ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
11
+ import { addOrUpdateEnvVar, getEnvVar, removeEnvVar } from '../utils/env-management.js';
12
+ import { formatFile } from '../utils/formatter.js';
13
+ import { getPackageManager } from '../utils/package-manager.js';
14
+ import { removeFigmaContentSystemId } from '../utils/payload-config-ast.js';
15
+ import { findPayloadConfig } from '../utils/payload-config-finder.js';
16
+ import { checkPackageInstalled, installPackage } from '../utils/payload-package-check.js';
17
+ import { getOwnVersion } from '../utils/version-check.js';
18
+ export async function upgradeCommand(options = {}) {
19
+ const cwd = process.cwd();
20
+ const { isDryRun = false } = options;
21
+ const changes = [];
22
+ const warnings = [];
23
+ const spinner = p.spinner();
24
+ spinner.start('Checking for needed migrations...');
25
+ spinner.stop('Checking for needed migrations...');
26
+ await migrateEnvRename({
27
+ changes,
28
+ cwd,
29
+ isDryRun
30
+ });
31
+ await migrateProjectId({
32
+ changes,
33
+ cwd,
34
+ isDryRun,
35
+ warnings
36
+ });
37
+ await ensureBootstrapCached({
38
+ changes,
39
+ cwd,
40
+ isDryRun
41
+ });
42
+ await migrateRemoveDeprecatedEnvVars({
43
+ changes,
44
+ cwd,
45
+ isDryRun,
46
+ warnings
47
+ });
48
+ await migrateConfigAst({
49
+ changes,
50
+ cwd,
51
+ isDryRun
52
+ });
53
+ await migratePackageVersion({
54
+ changes,
55
+ cwd,
56
+ isDryRun
57
+ });
58
+ for (const warning of warnings){
59
+ p.log.warning(warning);
60
+ }
61
+ if (changes.length === 0) {
62
+ p.log.success('Everything is up to date.');
63
+ return;
64
+ }
65
+ if (isDryRun) {
66
+ p.log.info(`Dry run: ${changes.length} change(s) detected.`);
67
+ } else {
68
+ p.log.success(`${changes.length} change(s) applied.`);
69
+ }
70
+ }
71
+ const DEPRECATED_ENV_VARS = [
72
+ 'FIGMA_CONTENT_API_CONTENT_SYSTEM_ID',
73
+ 'FIGMA_TENANT_ID',
74
+ 'FIGMA_OAUTH_CLIENT_ID',
75
+ 'FIGMA_OAUTH_CLIENT_SECRET'
76
+ ];
77
+ async function migrateEnvRename({ changes, cwd, isDryRun }) {
78
+ try {
79
+ const oldValue = await getEnvVar(cwd, 'FIGMA_ENV');
80
+ if (oldValue === null) {
81
+ return;
82
+ }
83
+ if (isDryRun) {
84
+ p.log.message(`${pc.dim('○')} Would rename FIGMA_ENV → FIGMA_INFRA_ENV`);
85
+ changes.push('Rename FIGMA_ENV → FIGMA_INFRA_ENV');
86
+ return;
87
+ }
88
+ await addOrUpdateEnvVar(cwd, 'FIGMA_INFRA_ENV', oldValue);
89
+ await removeEnvVar(cwd, 'FIGMA_ENV');
90
+ p.log.message(`${pc.green('●')} Env: Renamed FIGMA_ENV → FIGMA_INFRA_ENV`);
91
+ changes.push('Renamed FIGMA_ENV → FIGMA_INFRA_ENV');
92
+ } catch (error) {
93
+ const msg = error instanceof Error ? error.message : 'Unknown error';
94
+ p.log.warning(`Env rename migration failed: ${msg}`);
95
+ }
96
+ }
97
+ async function migrateProjectId({ changes, cwd, isDryRun, warnings = [] }) {
98
+ try {
99
+ const existingProjectId = await getEnvVar(cwd, 'FIGMA_PROJECT_ID');
100
+ if (existingProjectId) {
101
+ return;
102
+ }
103
+ const oldContentSystemId = await getEnvVar(cwd, 'FIGMA_CONTENT_API_CONTENT_SYSTEM_ID');
104
+ if (!oldContentSystemId) {
105
+ return;
106
+ }
107
+ if (isDryRun) {
108
+ p.log.message(`${pc.dim('○')} Would resolve FIGMA_PROJECT_ID from content system ID`);
109
+ changes.push('Resolve FIGMA_PROJECT_ID from content system ID');
110
+ return;
111
+ }
112
+ const store = getTokenStore();
113
+ const accessToken = await getValidAccessToken(store);
114
+ if (!accessToken) {
115
+ warnings.push('Not authenticated — could not resolve FIGMA_PROJECT_ID. Run `npx @payloadcms/figma login` first.');
116
+ return;
117
+ }
118
+ const cmsResourceId = await getCmsResourceId(accessToken, oldContentSystemId);
119
+ await addOrUpdateEnvVar(cwd, 'FIGMA_PROJECT_ID', cmsResourceId);
120
+ // Resolve actual environment name from bootstrap API
121
+ try {
122
+ const bootstrapInfo = await getBootstrapInfo(accessToken, cmsResourceId);
123
+ cacheAllEnvironments(store, cmsResourceId, bootstrapInfo);
124
+ const envName = bootstrapInfo.environments[0]?.name ?? 'production';
125
+ await addOrUpdateEnvVar(cwd, 'FIGMA_ENVIRONMENT_NAME', envName);
126
+ } catch {
127
+ await addOrUpdateEnvVar(cwd, 'FIGMA_ENVIRONMENT_NAME', 'production');
128
+ }
129
+ p.log.message(`${pc.green('●')} Env: Resolved FIGMA_PROJECT_ID from content system ID`);
130
+ changes.push('Resolved FIGMA_PROJECT_ID from content system ID');
131
+ } catch (error) {
132
+ const msg = error instanceof Error ? error.message : 'Unknown error';
133
+ p.log.warning(`Project ID migration failed: ${msg}`);
134
+ }
135
+ }
136
+ async function ensureBootstrapCached(params) {
137
+ try {
138
+ const projectId = await getEnvVar(params.cwd, 'FIGMA_PROJECT_ID');
139
+ if (!projectId) {
140
+ return;
141
+ }
142
+ const environmentName = await getEnvVar(params.cwd, 'FIGMA_ENVIRONMENT_NAME') ?? 'production';
143
+ const store = getTokenStore();
144
+ if (store.getBootstrapData(projectId, environmentName)) {
145
+ return;
146
+ }
147
+ const accessToken = await getValidAccessToken(store);
148
+ if (!accessToken) {
149
+ return;
150
+ }
151
+ const bootstrapInfo = await getBootstrapInfo(accessToken, projectId);
152
+ cacheAllEnvironments(store, projectId, bootstrapInfo);
153
+ // Fix FIGMA_ENVIRONMENT_NAME if it doesn't match any actual environment
154
+ const envNames = bootstrapInfo.environments.map((e)=>e.name);
155
+ if (!envNames.includes(environmentName) && envNames.length > 0) {
156
+ const correctName = envNames[0];
157
+ if (params.isDryRun) {
158
+ p.log.message(`${pc.dim('○')} Would fix FIGMA_ENVIRONMENT_NAME: ${environmentName} → ${correctName}`);
159
+ params.changes.push(`Fix FIGMA_ENVIRONMENT_NAME`);
160
+ return;
161
+ }
162
+ await addOrUpdateEnvVar(params.cwd, 'FIGMA_ENVIRONMENT_NAME', correctName);
163
+ p.log.message(`${pc.green('●')} Env: Fixed FIGMA_ENVIRONMENT_NAME: ${environmentName} → ${correctName}`);
164
+ params.changes.push(`Fixed FIGMA_ENVIRONMENT_NAME: ${environmentName} → ${correctName}`);
165
+ }
166
+ } catch {
167
+ // Best-effort — runtime will retry if needed
168
+ }
169
+ }
170
+ async function migrateRemoveDeprecatedEnvVars({ changes, cwd, isDryRun, warnings = [] }) {
171
+ try {
172
+ const presentVars = [];
173
+ for (const key of DEPRECATED_ENV_VARS){
174
+ const value = await getEnvVar(cwd, key);
175
+ if (value !== null) {
176
+ presentVars.push(key);
177
+ }
178
+ }
179
+ if (presentVars.length === 0) {
180
+ return;
181
+ }
182
+ // Only remove when bootstrap data is available (proves the project is set up correctly)
183
+ const projectId = await getEnvVar(cwd, 'FIGMA_PROJECT_ID');
184
+ if (!projectId) {
185
+ warnings.push('Cannot remove deprecated env vars without FIGMA_PROJECT_ID. Run upgrade again after project ID migration.');
186
+ return;
187
+ }
188
+ const environmentName = await getEnvVar(cwd, 'FIGMA_ENVIRONMENT_NAME') ?? 'production';
189
+ const store = getTokenStore();
190
+ const bootstrapData = store.getBootstrapData(projectId, environmentName);
191
+ if (!bootstrapData) {
192
+ // Try fetching from API
193
+ const accessToken = await getValidAccessToken(store);
194
+ if (accessToken) {
195
+ try {
196
+ const bootstrapInfo = await getBootstrapInfo(accessToken, projectId);
197
+ cacheAllEnvironments(store, projectId, bootstrapInfo);
198
+ } catch {
199
+ warnings.push('Could not verify bootstrap data — keeping deprecated env vars. Run `npx @payloadcms/figma init` to complete migration.');
200
+ return;
201
+ }
202
+ } else {
203
+ warnings.push('Not authenticated — keeping deprecated env vars. Run `npx @payloadcms/figma login` first.');
204
+ return;
205
+ }
206
+ }
207
+ if (isDryRun) {
208
+ p.log.message(`${pc.dim('○')} Would remove deprecated env vars: ${presentVars.join(', ')}`);
209
+ changes.push(`Remove deprecated env vars: ${presentVars.join(', ')}`);
210
+ return;
211
+ }
212
+ const removed = [];
213
+ for (const key of presentVars){
214
+ await removeEnvVar(cwd, key);
215
+ removed.push(key);
216
+ }
217
+ p.log.message(`${pc.green('●')} Env: Removed deprecated env vars: ${removed.join(', ')}`);
218
+ changes.push(`Removed deprecated env vars: ${removed.join(', ')}`);
219
+ } catch (error) {
220
+ const msg = error instanceof Error ? error.message : 'Unknown error';
221
+ p.log.warning(`Deprecated env var removal failed: ${msg}`);
222
+ }
223
+ }
224
+ async function migrateConfigAst({ changes, cwd, isDryRun }) {
225
+ try {
226
+ const configPath = await findPayloadConfig(cwd);
227
+ if (!configPath) {
228
+ return;
229
+ }
230
+ const project = new Project({
231
+ skipAddingFilesFromTsConfig: true
232
+ });
233
+ const sourceFile = project.addSourceFileAtPath(configPath);
234
+ const wouldRemove = removeFigmaContentSystemId(sourceFile);
235
+ if (!wouldRemove) {
236
+ return;
237
+ }
238
+ if (isDryRun) {
239
+ p.log.message(`${pc.dim('○')} Would remove contentSystemId from payload.config.ts`);
240
+ changes.push('Remove contentSystemId from payload.config.ts');
241
+ return;
242
+ }
243
+ // Re-parse fresh since the detection already mutated the AST
244
+ const freshProject = new Project({
245
+ skipAddingFilesFromTsConfig: true
246
+ });
247
+ const freshSourceFile = freshProject.addSourceFileAtPath(configPath);
248
+ removeFigmaContentSystemId(freshSourceFile);
249
+ await freshSourceFile.save();
250
+ try {
251
+ const packageManager = await getPackageManager(cwd);
252
+ await formatFile(configPath, packageManager);
253
+ } catch {
254
+ // Formatting is best-effort
255
+ }
256
+ p.log.message(`${pc.green('●')} Config: Removed contentSystemId from payload.config.ts`);
257
+ changes.push('Removed contentSystemId from payload.config.ts');
258
+ } catch (error) {
259
+ const msg = error instanceof Error ? error.message : 'Unknown error';
260
+ p.log.warning(`Config AST migration failed: ${msg}`);
261
+ }
262
+ }
263
+ async function migratePackageVersion({ changes, cwd, isDryRun }) {
264
+ try {
265
+ const isInstalled = await checkPackageInstalled(cwd, '@payloadcms/figma');
266
+ if (!isInstalled) {
267
+ return;
268
+ }
269
+ const cliDir = path.dirname(fileURLToPath(import.meta.url));
270
+ const isFromRegistry = cliDir.includes('node_modules') || cliDir.includes('.npm');
271
+ if (isFromRegistry) {
272
+ await migrateRegistryPackageVersion({
273
+ changes,
274
+ cliDir,
275
+ cwd,
276
+ isDryRun
277
+ });
278
+ } else {
279
+ await migrateLocalPackageVersion({
280
+ changes,
281
+ cliDir,
282
+ cwd,
283
+ isDryRun
284
+ });
285
+ }
286
+ } catch (error) {
287
+ const msg = error instanceof Error ? error.message : 'Unknown error';
288
+ p.log.warning(`Package version sync failed: ${msg}`);
289
+ }
290
+ }
291
+ async function migrateRegistryPackageVersion({ changes, cliDir: _cliDir, cwd, isDryRun }) {
292
+ const cliVersion = await getOwnVersion();
293
+ const pkgJsonPath = path.join(cwd, 'package.json');
294
+ const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf-8'));
295
+ const installedVersion = pkgJson.dependencies?.['@payloadcms/figma']?.replace(/^[\^~>=<]+/, '');
296
+ if (!installedVersion || installedVersion === cliVersion) {
297
+ return;
298
+ }
299
+ if (isDryRun) {
300
+ p.log.message(`${pc.dim('○')} Would update @payloadcms/figma ${installedVersion} → ${cliVersion}`);
301
+ changes.push(`Update @payloadcms/figma ${installedVersion} → ${cliVersion}`);
302
+ return;
303
+ }
304
+ const packageManager = await getPackageManager(cwd);
305
+ await installPackage(cwd, '@payloadcms/figma', packageManager, cliVersion);
306
+ p.log.message(`${pc.green('●')} Package: Updated @payloadcms/figma ${installedVersion} → ${cliVersion}`);
307
+ changes.push(`Updated @payloadcms/figma ${installedVersion} → ${cliVersion}`);
308
+ }
309
+ async function migrateLocalPackageVersion({ changes, cliDir, cwd, isDryRun }) {
310
+ if (isDryRun) {
311
+ p.log.message(`${pc.dim('○')} Would install @payloadcms/figma from local build`);
312
+ changes.push('Install @payloadcms/figma from local build');
313
+ return;
314
+ }
315
+ // Dynamic import — findPackageRoot and packLocalPackage will be exported in a later task
316
+ const { findPackageRoot, packLocalPackage } = await import('../utils/handle-upgrade.js');
317
+ const pkgDir = await findPackageRoot(cliDir);
318
+ if (!pkgDir) {
319
+ return;
320
+ }
321
+ const packageManager = await getPackageManager(cwd);
322
+ const tgzPath = packLocalPackage(pkgDir);
323
+ const destTgz = path.join(cwd, path.basename(tgzPath));
324
+ await fs.rename(tgzPath, destTgz);
325
+ await installPackage(cwd, destTgz, packageManager);
326
+ await fs.unlink(destTgz).catch(()=>{});
327
+ p.log.message(`${pc.green('●')} Package: Installed @payloadcms/figma from local build`);
328
+ changes.push('Installed @payloadcms/figma from local build');
329
+ }
330
+
331
+ //# sourceMappingURL=upgrade.js.map
package/dist/constants.js CHANGED
@@ -47,8 +47,8 @@ export function getInfraEnvironment() {
47
47
  if (process.env.FIGMA_INFRA_ENV?.toLowerCase() === 'staging') {
48
48
  return 'staging';
49
49
  }
50
- // Fall back to .env file in cwd
51
- const envFileValue = getEnvVarSync(process.cwd(), 'FIGMA_INFRA_ENV');
50
+ // Fall back to .env file in cwd (check both new and old env var names)
51
+ const envFileValue = getEnvVarSync(process.cwd(), 'FIGMA_INFRA_ENV') ?? getEnvVarSync(process.cwd(), 'FIGMA_ENV');
52
52
  if (envFileValue) {
53
53
  const env = envFileValue.toLowerCase();
54
54
  if (env === 'staging') {
@@ -1494,6 +1494,117 @@ export type paths = {
1494
1494
  };
1495
1495
  };
1496
1496
  };
1497
+ /** @description Unauthorized */
1498
+ 401: {
1499
+ headers: {
1500
+ [name: string]: unknown;
1501
+ };
1502
+ content: {
1503
+ 'application/json': {
1504
+ /** @example Error message describing the issue. */
1505
+ message: string;
1506
+ };
1507
+ };
1508
+ };
1509
+ /** @description Forbidden */
1510
+ 403: {
1511
+ headers: {
1512
+ [name: string]: unknown;
1513
+ };
1514
+ content: {
1515
+ 'application/json': {
1516
+ /** @example Error message describing the issue. */
1517
+ message: string;
1518
+ };
1519
+ };
1520
+ };
1521
+ /** @description Not Found */
1522
+ 404: {
1523
+ headers: {
1524
+ [name: string]: unknown;
1525
+ };
1526
+ content: {
1527
+ 'application/json': {
1528
+ /** @example Error message describing the issue. */
1529
+ message: string;
1530
+ };
1531
+ };
1532
+ };
1533
+ /** @description Internal Server Error */
1534
+ 500: {
1535
+ headers: {
1536
+ [name: string]: unknown;
1537
+ };
1538
+ content: {
1539
+ 'application/json': {
1540
+ /** @example Error message describing the issue. */
1541
+ message: string;
1542
+ };
1543
+ };
1544
+ };
1545
+ };
1546
+ };
1547
+ delete?: never;
1548
+ options?: never;
1549
+ head?: never;
1550
+ patch?: never;
1551
+ trace?: never;
1552
+ };
1553
+ '/api/v0/documents:findDistinct': {
1554
+ parameters: {
1555
+ query?: never;
1556
+ header?: never;
1557
+ path?: never;
1558
+ cookie?: never;
1559
+ };
1560
+ get?: never;
1561
+ put?: never;
1562
+ post: {
1563
+ parameters: {
1564
+ query?: never;
1565
+ header?: never;
1566
+ path?: never;
1567
+ cookie?: never;
1568
+ };
1569
+ requestBody?: {
1570
+ content: {
1571
+ 'application/json': components['schemas']['FindDistinctDocumentsRequest'];
1572
+ };
1573
+ };
1574
+ responses: {
1575
+ /** @description Distinct values found */
1576
+ 200: {
1577
+ headers: {
1578
+ [name: string]: unknown;
1579
+ };
1580
+ content: {
1581
+ 'application/json': components['schemas']['FindDistinctDocumentsResponse'];
1582
+ };
1583
+ };
1584
+ /** @description Bad Request */
1585
+ 400: {
1586
+ headers: {
1587
+ [name: string]: unknown;
1588
+ };
1589
+ content: {
1590
+ 'application/json': {
1591
+ /** @example Error message describing the issue. */
1592
+ message: string;
1593
+ };
1594
+ };
1595
+ };
1596
+ /** @description Unauthorized */
1597
+ 401: {
1598
+ headers: {
1599
+ [name: string]: unknown;
1600
+ };
1601
+ content: {
1602
+ 'application/json': {
1603
+ /** @example Error message describing the issue. */
1604
+ message: string;
1605
+ };
1606
+ };
1607
+ };
1497
1608
  /** @description Forbidden */
1498
1609
  403: {
1499
1610
  headers: {
@@ -2953,12 +3064,21 @@ export type components = {
2953
3064
  contentSystemScope?: string;
2954
3065
  };
2955
3066
  HealthResponse: {
2956
- /** @example ok */
2957
- status: string;
2958
- /** @example connected */
2959
- database: string;
2960
- /** @example available */
2961
- s3: string;
3067
+ /**
3068
+ * @example ok
3069
+ * @enum {string}
3070
+ */
3071
+ status: 'ok';
3072
+ /**
3073
+ * @example connected
3074
+ * @enum {string}
3075
+ */
3076
+ database: 'connected' | 'disconnected';
3077
+ /**
3078
+ * @example available
3079
+ * @enum {string}
3080
+ */
3081
+ s3: 'available' | 'unavailable';
2962
3082
  /** @example 2026-01-01T00:00:00.000Z */
2963
3083
  timestamp: string;
2964
3084
  };
@@ -3177,14 +3297,51 @@ export type components = {
3177
3297
  count: number;
3178
3298
  };
3179
3299
  };
3300
+ /** @example es */
3301
+ LocaleClause: string;
3180
3302
  /**
3181
- * @example array
3182
- * @enum {string}
3303
+ * @example {
3304
+ * "paths": [
3305
+ * "field_a",
3306
+ * "field_b"
3307
+ * ]
3308
+ * }
3183
3309
  */
3184
- PathType: 'array';
3310
+ UniquePath: {
3311
+ paths: string[];
3312
+ };
3313
+ PathTypeRelationship: {
3314
+ /** @enum {string} */
3315
+ type: 'relationship';
3316
+ collection: string | string[];
3317
+ hasMany?: boolean;
3318
+ };
3319
+ PathTypeJoin: {
3320
+ /** @enum {string} */
3321
+ type: 'join';
3322
+ collection: string | string[];
3323
+ hasMany?: boolean;
3324
+ on: string;
3325
+ };
3326
+ /** @enum {string} */
3327
+ PathTypeBlocks: 'blocks';
3328
+ /** @example array */
3329
+ PathType: 'array' | components['schemas']['PathTypeRelationship'] | components['schemas']['PathTypeJoin'] | components['schemas']['PathTypeBlocks'];
3185
3330
  /**
3186
3331
  * @example {
3187
- * "author.tagIds": "array"
3332
+ * "author.tagIds": "array",
3333
+ * "author": {
3334
+ * "type": "relationship",
3335
+ * "collection": "users",
3336
+ * "hasMany": false
3337
+ * },
3338
+ * "relatedPosts": {
3339
+ * "type": "join",
3340
+ * "collection": "posts",
3341
+ * "hasMany": true,
3342
+ * "on": "category"
3343
+ * },
3344
+ * "blocks": "blocks"
3188
3345
  * }
3189
3346
  */
3190
3347
  PathTypesMeta: {
@@ -3192,6 +3349,7 @@ export type components = {
3192
3349
  };
3193
3350
  RequestMeta: {
3194
3351
  localizedPaths?: string[];
3352
+ uniquePaths?: components['schemas']['UniquePath'][];
3195
3353
  pathTypes?: components['schemas']['PathTypesMeta'];
3196
3354
  };
3197
3355
  DataValue: string | number | boolean | unknown | components['schemas']['DataValue'][] | {
@@ -3227,6 +3385,7 @@ export type components = {
3227
3385
  contentSystemId: string;
3228
3386
  /** @example posts */
3229
3387
  collection: string;
3388
+ locale?: components['schemas']['LocaleClause'];
3230
3389
  meta?: components['schemas']['RequestMeta'];
3231
3390
  where?: components['schemas']['WhereClause'];
3232
3391
  };
@@ -3291,29 +3450,46 @@ export type components = {
3291
3450
  page?: number;
3292
3451
  sort?: components['schemas']['SortClause'];
3293
3452
  }[];
3294
- /** @example es */
3295
- LocaleClause: string;
3296
3453
  /**
3297
3454
  * @example {
3298
- * "mode": "include",
3299
- * "paths": [
3300
- * "title",
3301
- * "author.name",
3302
- * "tags"
3303
- * ]
3455
+ * "title": true,
3456
+ * "author": {
3457
+ * "name": true,
3458
+ * "email": true
3459
+ * },
3460
+ * "tags": true
3304
3461
  * }
3305
3462
  */
3306
- SelectClause: {
3307
- /** @enum {string} */
3308
- mode: 'exclude' | 'include';
3309
- paths: string[];
3463
+ IncludeSelectClause: {
3464
+ [key: string]: true | {
3465
+ [key: string]: components['schemas']['IncludeSelectClause'];
3466
+ };
3467
+ };
3468
+ /**
3469
+ * @example {
3470
+ * "title": false,
3471
+ * "author": {
3472
+ * "name": false,
3473
+ * "email": false
3474
+ * },
3475
+ * "tags": false
3476
+ * }
3477
+ */
3478
+ ExcludeSelectClause: {
3479
+ [key: string]: false | {
3480
+ [key: string]: components['schemas']['ExcludeSelectClause'];
3481
+ };
3310
3482
  };
3483
+ /** @description Field selection - use include mode (true) or exclude mode (false), but not both */
3484
+ SelectClause: components['schemas']['IncludeSelectClause'] | components['schemas']['ExcludeSelectClause'];
3311
3485
  CreateDocumentRequest: {
3312
3486
  /** @example cms-xxxxx-xxxxx */
3313
3487
  contentSystemId: string;
3314
3488
  /** @example posts */
3315
3489
  collection: string;
3316
3490
  doc: components['schemas']['DocumentData'];
3491
+ locale?: components['schemas']['LocaleClause'];
3492
+ meta?: components['schemas']['RequestMeta'];
3317
3493
  returning?: false | {
3318
3494
  join?: components['schemas']['JoinClause'];
3319
3495
  locale?: components['schemas']['LocaleClause'];
@@ -3353,6 +3529,47 @@ export type components = {
3353
3529
  sort?: components['schemas']['SortClause'];
3354
3530
  where?: components['schemas']['WhereClause'];
3355
3531
  };
3532
+ FindDistinctDocumentsResponse: {
3533
+ result: {
3534
+ /**
3535
+ * @example [
3536
+ * "value1",
3537
+ * "value2"
3538
+ * ]
3539
+ */
3540
+ data: unknown[];
3541
+ totalDocs: number;
3542
+ limit: number;
3543
+ page: number;
3544
+ totalPages: number;
3545
+ pagingCounter: number;
3546
+ hasPrevPage: boolean;
3547
+ hasNextPage: boolean;
3548
+ prevPage: number | null;
3549
+ nextPage: number | null;
3550
+ };
3551
+ };
3552
+ FindDistinctDocumentsRequest: {
3553
+ /** @example cms-xxxxx-xxxxx */
3554
+ contentSystemId: string;
3555
+ /** @example posts */
3556
+ collection: string;
3557
+ /**
3558
+ * @description Path to the field to get distinct values for
3559
+ * @example title
3560
+ */
3561
+ distinctBy: string;
3562
+ join?: components['schemas']['JoinClause'];
3563
+ /** @example 10 */
3564
+ limit?: number;
3565
+ locale?: components['schemas']['LocaleClause'];
3566
+ /** @example 1 */
3567
+ page?: number;
3568
+ meta?: components['schemas']['RequestMeta'];
3569
+ select?: components['schemas']['SelectClause'];
3570
+ sort?: components['schemas']['SortClause'];
3571
+ where?: components['schemas']['WhereClause'];
3572
+ };
3356
3573
  UpdateDocumentResponse: {
3357
3574
  result?: {
3358
3575
  /** @example 1 */
@@ -3388,6 +3605,7 @@ export type components = {
3388
3605
  doc: components['schemas']['DataWithOperations'];
3389
3606
  /** @example 10 */
3390
3607
  limit?: number;
3608
+ locale?: components['schemas']['LocaleClause'];
3391
3609
  meta?: components['schemas']['RequestMeta'];
3392
3610
  returning?: false | {
3393
3611
  join?: components['schemas']['JoinClause'];
@@ -3396,7 +3614,6 @@ export type components = {
3396
3614
  };
3397
3615
  sort?: components['schemas']['SortClause'];
3398
3616
  where: components['schemas']['WhereClause'];
3399
- locale?: components['schemas']['LocaleClause'];
3400
3617
  };
3401
3618
  DeleteDocumentResponse: {
3402
3619
  result?: {
@@ -3414,6 +3631,7 @@ export type components = {
3414
3631
  contentSystemId: string;
3415
3632
  /** @example posts */
3416
3633
  collection: string;
3634
+ locale?: components['schemas']['LocaleClause'];
3417
3635
  meta?: components['schemas']['RequestMeta'];
3418
3636
  returning?: false | {
3419
3637
  join?: components['schemas']['JoinClause'];
@@ -3421,7 +3639,6 @@ export type components = {
3421
3639
  select?: components['schemas']['SelectClause'];
3422
3640
  };
3423
3641
  where: components['schemas']['WhereClause'];
3424
- locale?: components['schemas']['LocaleClause'];
3425
3642
  };
3426
3643
  CountDocumentVersionResponse: {
3427
3644
  result: {
@@ -3434,6 +3651,7 @@ export type components = {
3434
3651
  contentSystemId: string;
3435
3652
  /** @example posts */
3436
3653
  collection: string;
3654
+ locale?: components['schemas']['LocaleClause'];
3437
3655
  meta?: components['schemas']['RequestMeta'];
3438
3656
  where?: components['schemas']['WhereClause'];
3439
3657
  };
@@ -3495,6 +3713,8 @@ export type components = {
3495
3713
  parent?: string | number;
3496
3714
  version: components['schemas']['DocumentData'] & unknown;
3497
3715
  };
3716
+ locale?: components['schemas']['LocaleClause'];
3717
+ meta?: components['schemas']['RequestMeta'];
3498
3718
  returning?: false | {
3499
3719
  join?: components['schemas']['JoinClause'];
3500
3720
  locale?: components['schemas']['LocaleClause'];
@@ -3569,9 +3789,9 @@ export type components = {
3569
3789
  where: components['schemas']['WhereClause'];
3570
3790
  /** @example 10 */
3571
3791
  limit?: number;
3792
+ locale?: components['schemas']['LocaleClause'];
3572
3793
  meta?: components['schemas']['RequestMeta'];
3573
3794
  sort?: components['schemas']['SortClause'];
3574
- locale?: components['schemas']['LocaleClause'];
3575
3795
  returning?: false | {
3576
3796
  join?: components['schemas']['JoinClause'];
3577
3797
  locale?: components['schemas']['LocaleClause'];
@@ -3594,6 +3814,7 @@ export type components = {
3594
3814
  contentSystemId: string;
3595
3815
  /** @example posts */
3596
3816
  collection: string;
3817
+ locale?: components['schemas']['LocaleClause'];
3597
3818
  meta?: components['schemas']['RequestMeta'];
3598
3819
  returning?: false | {
3599
3820
  join?: components['schemas']['JoinClause'];
@@ -62,18 +62,6 @@ function missingOAuthCredential(name) {
62
62
  }
63
63
  }
64
64
  export async function buildFigmaConfig(config) {
65
- // Auto-migrate old env vars in local dev (never in Lambda/production)
66
- if (process.env.NODE_ENV !== 'production' && !process.env.AWS_LAMBDA_FUNCTION_NAME) {
67
- try {
68
- const { handleEnvUpgrade } = await import('../utils/handle-upgrade.js');
69
- const upgradeChanges = await handleEnvUpgrade(process.cwd());
70
- if (upgradeChanges.length > 0) {
71
- log.warning(`Auto-migrated .env: ${upgradeChanges.join(', ')}`);
72
- }
73
- } catch {
74
- // Never block config build due to upgrade check
75
- }
76
- }
77
65
  const envConfig = getEnvConfig();
78
66
  // Resolve contentSystemId: config first, then local store fallback (dev)
79
67
  let { contentSystemId } = config.figma;
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type arg from 'arg';
2
2
  export interface Args extends arg.Spec {
3
3
  '--debug': BooleanConstructor;
4
+ '--dry-run': BooleanConstructor;
4
5
  '--env': StringConstructor;
5
6
  '--force': BooleanConstructor;
6
7
  '--help': BooleanConstructor;
@@ -1,12 +1,9 @@
1
1
  /**
2
- * Migrate .env file from old env var format to new format.
3
- * Non-blocking — returns list of changes made, never throws.
2
+ * Walk up from a directory to find the nearest package.json with name "@payloadcms/figma".
4
3
  */
5
- export declare function handleEnvUpgrade(projectPath: string): Promise<string[]>;
4
+ export declare function findPackageRoot(startDir: string): Promise<null | string>;
6
5
  /**
7
- * Full upgrade migration: .env + payload.config.ts AST changes + version sync.
8
- * Called from CLI entry point before command routing.
9
- * Non-blocking — returns list of changes made, never throws.
6
+ * Build and pack the local @payloadcms/figma package, returning the tgz path.
10
7
  */
11
- export declare function handleUpgrade(projectPath: string): Promise<string[]>;
8
+ export declare function packLocalPackage(pkgDir: string): string;
12
9
  //# sourceMappingURL=handle-upgrade.d.ts.map
@@ -1,36 +1,9 @@
1
1
  import { execSync } from 'child_process';
2
2
  import fs from 'fs/promises';
3
3
  import path from 'path';
4
- import { Project } from 'ts-morph';
5
- import { fileURLToPath } from 'url';
6
- import { getBootstrapInfo, getCmsResourceId } from '../api/control-plane.js';
7
- import { getValidAccessToken } from '../auth/oauth-flow.js';
8
- import { getTokenStore } from '../auth/token-store.js';
9
- import { cacheAllEnvironments } from './cache-bootstrap.js';
10
- import { addOrUpdateEnvVar, getEnvVar, removeEnvVar } from './env-management.js';
11
- import { formatFile } from './formatter.js';
12
- import * as log from './log.js';
13
- import { getPackageManager } from './package-manager.js';
14
- import { removeFigmaContentSystemId } from './payload-config-ast.js';
15
- import { findPayloadConfig } from './payload-config-finder.js';
16
- import { checkPackageInstalled, installPackage } from './payload-package-check.js';
17
- import { getOwnVersion } from './version-check.js';
18
- const OLD_ENV_VARS = [
19
- 'FIGMA_ENV',
20
- 'FIGMA_CONTENT_API_CONTENT_SYSTEM_ID',
21
- 'FIGMA_TENANT_ID',
22
- 'FIGMA_OAUTH_CLIENT_ID',
23
- 'FIGMA_OAUTH_CLIENT_SECRET'
24
- ];
25
- const REMOVABLE_ENV_VARS = [
26
- 'FIGMA_CONTENT_API_CONTENT_SYSTEM_ID',
27
- 'FIGMA_TENANT_ID',
28
- 'FIGMA_OAUTH_CLIENT_ID',
29
- 'FIGMA_OAUTH_CLIENT_SECRET'
30
- ];
31
4
  /**
32
5
  * Walk up from a directory to find the nearest package.json with name "@payloadcms/figma".
33
- */ async function findPackageRoot(startDir) {
6
+ */ export async function findPackageRoot(startDir) {
34
7
  let dir = startDir;
35
8
  while(true){
36
9
  const pkgPath = path.join(dir, 'package.json');
@@ -51,7 +24,7 @@ const REMOVABLE_ENV_VARS = [
51
24
  }
52
25
  /**
53
26
  * Build and pack the local @payloadcms/figma package, returning the tgz path.
54
- */ function packLocalPackage(pkgDir) {
27
+ */ export function packLocalPackage(pkgDir) {
55
28
  execSync('pnpm build', {
56
29
  cwd: pkgDir,
57
30
  stdio: 'pipe'
@@ -64,179 +37,5 @@ const REMOVABLE_ENV_VARS = [
64
37
  const tgzPath = path.join(pkgDir, tgzName);
65
38
  return tgzPath;
66
39
  }
67
- /**
68
- * Attempt to resolve bootstrap data: check cache first, then API call.
69
- */ async function resolveBootstrapData(projectId, environmentName) {
70
- const store = getTokenStore();
71
- const cached = store.getBootstrapData(projectId, environmentName);
72
- if (cached) {
73
- return cached;
74
- }
75
- try {
76
- const accessToken = await getValidAccessToken(store);
77
- if (!accessToken) {
78
- return null;
79
- }
80
- const bootstrapInfo = await getBootstrapInfo(accessToken, projectId);
81
- cacheAllEnvironments(store, projectId, bootstrapInfo);
82
- return store.getBootstrapData(projectId, environmentName);
83
- } catch {
84
- return null;
85
- }
86
- }
87
- /**
88
- * Migrate .env file from old env var format to new format.
89
- * Non-blocking — returns list of changes made, never throws.
90
- */ export async function handleEnvUpgrade(projectPath) {
91
- try {
92
- const oldValues = {};
93
- let hasOldVars = false;
94
- for (const key of OLD_ENV_VARS){
95
- const value = await getEnvVar(projectPath, key);
96
- oldValues[key] = value;
97
- if (value !== null) {
98
- hasOldVars = true;
99
- }
100
- }
101
- if (!hasOldVars) {
102
- return [];
103
- }
104
- const changes = [];
105
- // Rename FIGMA_ENV → FIGMA_INFRA_ENV
106
- if (oldValues.FIGMA_ENV !== null) {
107
- await addOrUpdateEnvVar(projectPath, 'FIGMA_INFRA_ENV', oldValues.FIGMA_ENV);
108
- await removeEnvVar(projectPath, 'FIGMA_ENV');
109
- changes.push('Renamed FIGMA_ENV → FIGMA_INFRA_ENV');
110
- }
111
- // Remove old vars if bootstrap data is available
112
- const projectId = await getEnvVar(projectPath, 'FIGMA_PROJECT_ID');
113
- const environmentName = await getEnvVar(projectPath, 'FIGMA_ENVIRONMENT_NAME') ?? 'production';
114
- if (projectId) {
115
- const bootstrapData = await resolveBootstrapData(projectId, environmentName);
116
- if (bootstrapData) {
117
- const removed = [];
118
- for (const key of REMOVABLE_ENV_VARS){
119
- if (oldValues[key] !== null) {
120
- await removeEnvVar(projectPath, key);
121
- removed.push(key);
122
- }
123
- }
124
- if (removed.length > 0) {
125
- changes.push(`Removed deprecated env vars: ${removed.join(', ')}`);
126
- }
127
- } else {
128
- log.warning('Could not resolve bootstrap data — keeping deprecated env vars. ' + 'Run `npx @payloadcms/figma init` to complete migration.');
129
- }
130
- }
131
- return changes;
132
- } catch (error) {
133
- log.warning(`Upgrade migration failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
134
- return [];
135
- }
136
- }
137
- /**
138
- * Full upgrade migration: .env + payload.config.ts AST changes + version sync.
139
- * Called from CLI entry point before command routing.
140
- * Non-blocking — returns list of changes made, never throws.
141
- */ export async function handleUpgrade(projectPath) {
142
- const changes = await handleEnvUpgrade(projectPath);
143
- // Resolve CMS Resource ID from old content system ID via dataset API
144
- try {
145
- const existingProjectId = await getEnvVar(projectPath, 'FIGMA_PROJECT_ID');
146
- if (!existingProjectId) {
147
- const oldContentSystemId = await getEnvVar(projectPath, 'FIGMA_CONTENT_API_CONTENT_SYSTEM_ID');
148
- if (oldContentSystemId) {
149
- const store = getTokenStore();
150
- const accessToken = await getValidAccessToken(store);
151
- if (accessToken) {
152
- const cmsResourceId = await getCmsResourceId(accessToken, oldContentSystemId);
153
- await addOrUpdateEnvVar(projectPath, 'FIGMA_PROJECT_ID', cmsResourceId);
154
- await addOrUpdateEnvVar(projectPath, 'FIGMA_ENVIRONMENT_NAME', 'production');
155
- changes.push('Resolved FIGMA_PROJECT_ID from content system ID');
156
- // Fetch bootstrap data and cache it, then remove old vars
157
- try {
158
- const bootstrapInfo = await getBootstrapInfo(accessToken, cmsResourceId);
159
- cacheAllEnvironments(store, cmsResourceId, bootstrapInfo);
160
- const removed = [];
161
- for (const key of REMOVABLE_ENV_VARS){
162
- const value = await getEnvVar(projectPath, key);
163
- if (value !== null) {
164
- await removeEnvVar(projectPath, key);
165
- removed.push(key);
166
- }
167
- }
168
- if (removed.length > 0) {
169
- changes.push(`Removed deprecated env vars: ${removed.join(', ')}`);
170
- }
171
- } catch {
172
- log.warning('Could not fetch bootstrap data — keeping deprecated env vars. ' + 'Run `npx @payloadcms/figma init` to complete migration.');
173
- }
174
- } else {
175
- log.warning('Not authenticated — could not resolve FIGMA_PROJECT_ID. ' + 'Run `npx @payloadcms/figma init` to complete migration.');
176
- }
177
- }
178
- }
179
- } catch (error) {
180
- log.warning(`Project ID migration failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
181
- }
182
- try {
183
- const configPath = await findPayloadConfig(projectPath);
184
- if (configPath) {
185
- const project = new Project({
186
- skipAddingFilesFromTsConfig: true
187
- });
188
- const sourceFile = project.addSourceFileAtPath(configPath);
189
- const removed = removeFigmaContentSystemId(sourceFile);
190
- if (removed) {
191
- await sourceFile.save();
192
- try {
193
- const packageManager = await getPackageManager(projectPath);
194
- await formatFile(configPath, packageManager);
195
- } catch {
196
- // Formatting is best-effort
197
- }
198
- changes.push('Removed contentSystemId from payload.config.ts');
199
- }
200
- }
201
- } catch (error) {
202
- log.warning(`Config migration failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
203
- }
204
- // Sync @payloadcms/figma version in project's package.json
205
- try {
206
- const isInstalled = await checkPackageInstalled(projectPath, '@payloadcms/figma');
207
- if (isInstalled) {
208
- const cliDir = path.dirname(fileURLToPath(import.meta.url));
209
- const isFromRegistry = cliDir.includes('node_modules') || cliDir.includes('.npm');
210
- if (isFromRegistry) {
211
- // Registry install: sync to CLI version
212
- const cliVersion = await getOwnVersion();
213
- const pkgJsonPath = path.join(projectPath, 'package.json');
214
- const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf-8'));
215
- const installedVersion = pkgJson.dependencies?.['@payloadcms/figma']?.replace(/^[\^~>=<]+/, '');
216
- if (installedVersion && installedVersion !== cliVersion) {
217
- const packageManager = await getPackageManager(projectPath);
218
- await installPackage(projectPath, '@payloadcms/figma', packageManager, cliVersion);
219
- changes.push(`Updated @payloadcms/figma ${installedVersion} → ${cliVersion}`);
220
- }
221
- } else {
222
- // Local CLI: pack and install tgz into the project
223
- const pkgDir = await findPackageRoot(cliDir);
224
- if (pkgDir) {
225
- log.debug('CLI running from local path — packing and installing into project...');
226
- const packageManager = await getPackageManager(projectPath);
227
- const tgzPath = packLocalPackage(pkgDir);
228
- const destTgz = path.join(projectPath, path.basename(tgzPath));
229
- await fs.rename(tgzPath, destTgz);
230
- await installPackage(projectPath, destTgz, packageManager);
231
- await fs.unlink(destTgz).catch(()=>{});
232
- changes.push('Installed @payloadcms/figma from local build');
233
- }
234
- }
235
- }
236
- } catch (error) {
237
- log.warning(`Package version sync failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
238
- }
239
- return changes;
240
- }
241
40
 
242
41
  //# sourceMappingURL=handle-upgrade.js.map
@@ -17,6 +17,7 @@ export function helpMessage() {
17
17
  ${pc.cyan('debug')} Show debug info for troubleshooting
18
18
  ${pc.cyan('env')} Switch active environment
19
19
  ${pc.cyan('deploy')} Deploy your project to Figma
20
+ ${pc.cyan('upgrade')} Run upgrade migrations
20
21
  ${pc.cyan('build-lambda-zip')} Build Lambda deployment zip
21
22
 
22
23
  ${pc.bold('OPTIONS')}
@@ -50,6 +51,11 @@ export function helpMessage() {
50
51
  ${pc.dim('--yes, -y')} Skip confirmation prompts
51
52
  ${pc.dim('--skip-build')} Skip building and use existing build
52
53
 
54
+ ${pc.bold('UPGRADE COMMAND')}
55
+
56
+ ${pc.cyan('@payloadcms/figma upgrade')} Run all upgrade migrations
57
+ ${pc.dim('--dry-run')} Preview changes without applying
58
+
53
59
  ${pc.bold('GLOBAL OPTIONS')}
54
60
 
55
61
  ${pc.dim('--infra-env <env>')} Target infrastructure (production or staging)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payloadcms/figma",
3
- "version": "0.0.1-alpha.52",
3
+ "version": "0.0.1-alpha.54",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "type": "module",
6
6
  "exports": {