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

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.
@@ -151,4 +151,12 @@ export type BootstrapInfo = {
151
151
  * @param environmentName - Optional environment name filter
152
152
  */
153
153
  export declare function getBootstrapInfo(accessToken: string, cmsResourceId: string, environmentName?: string): Promise<BootstrapInfo>;
154
+ /**
155
+ * Resolve a CMS Resource ID from a legacy dataset (content system) ID.
156
+ * Maps to: GET /v1/cms/dataset/:datasetId
157
+ *
158
+ * Used during upgrade migration to convert old FIGMA_CONTENT_API_CONTENT_SYSTEM_ID
159
+ * into the new FIGMA_PROJECT_ID (CMS Resource ID).
160
+ */
161
+ export declare function getCmsResourceId(accessToken: string, datasetId: string): Promise<string>;
154
162
  //# sourceMappingURL=control-plane.d.ts.map
@@ -276,6 +276,27 @@ import * as log from '../utils/log.js';
276
276
  }
277
277
  };
278
278
  }
279
+ /**
280
+ * Resolve a CMS Resource ID from a legacy dataset (content system) ID.
281
+ * Maps to: GET /v1/cms/dataset/:datasetId
282
+ *
283
+ * Used during upgrade migration to convert old FIGMA_CONTENT_API_CONTENT_SYSTEM_ID
284
+ * into the new FIGMA_PROJECT_ID (CMS Resource ID).
285
+ */ export async function getCmsResourceId(accessToken, datasetId) {
286
+ const url = `${getControlPlaneBaseUrl()}/v1/cms/dataset/${datasetId}`;
287
+ log.debug(`Calling getCmsResourceId API at ${url}`);
288
+ const response = await controlPlaneFetch({
289
+ context: 'get CMS resource ID from dataset',
290
+ options: {
291
+ headers: {
292
+ Authorization: `Bearer ${accessToken}`
293
+ }
294
+ },
295
+ url
296
+ });
297
+ const data = await response.json();
298
+ return data.meta.cms_resource_id;
299
+ }
279
300
  const MAX_RETRIES = 3;
280
301
  const INITIAL_RETRY_DELAY = 1000;
281
302
  function sleep(ms) {
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { listTokensCommand } from './commands/list-tokens.js';
10
10
  import { loginCommand } from './commands/login.js';
11
11
  import { logoutCommand } from './commands/logout.js';
12
12
  import { setInfraEnvironment } from './constants.js';
13
+ import { handleUpgrade } from './utils/handle-upgrade.js';
13
14
  import { helpMessage } from './utils/messages.js';
14
15
  /**
15
16
  * Entrypoint for bin/cli.js
@@ -85,6 +86,15 @@ class Main {
85
86
  helpMessage();
86
87
  process.exit(0);
87
88
  }
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
+ }
88
98
  // Debug command outputs plain text for copy-paste — skip styled intro
89
99
  if (subcommand === 'debug') {
90
100
  await debugCommand();
@@ -8,20 +8,16 @@ import type { Config, SanitizedConfig } from 'payload';
8
8
  * - `editor`: Optional (defaults to lexicalEditor() if not provided)
9
9
  *
10
10
  * @example
11
- * // Minimal config
11
+ * // Minimal config (contentSystemId resolved from bootstrap data)
12
12
  * const config: FigmaConfig = {
13
- * figma: {
14
- * contentSystemId: process.env.FIGMA_CONTENT_API_CONTENT_SYSTEM_ID!
15
- * },
13
+ * figma: {},
16
14
  * collections: [...]
17
15
  * }
18
16
  *
19
17
  * @example
20
18
  * // With custom editor
21
19
  * const config: FigmaConfig = {
22
- * figma: {
23
- * contentSystemId: process.env.FIGMA_CONTENT_API_CONTENT_SYSTEM_ID!
24
- * },
20
+ * figma: {},
25
21
  * collections: [...],
26
22
  * editor: lexicalEditor({ features: [...] })
27
23
  * }
@@ -30,7 +26,6 @@ import type { Config, SanitizedConfig } from 'payload';
30
26
  * // Disable Content System (use custom db)
31
27
  * const config: FigmaConfig = {
32
28
  * figma: {
33
- * contentSystemId: process.env.FIGMA_CONTENT_API_CONTENT_SYSTEM_ID!,
34
29
  * useContentSystem: false
35
30
  * },
36
31
  * db: mongooseAdapter({ url: process.env.DATABASE_URI }),
@@ -41,7 +36,7 @@ export type FigmaConfig = {
41
36
  editor?: Config['editor'];
42
37
  } & {
43
38
  figma: {
44
- contentSystemId: string;
39
+ contentSystemId?: string;
45
40
  storage?: boolean;
46
41
  useContentSystem?: boolean;
47
42
  };
@@ -62,6 +62,18 @@ 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
+ }
65
77
  const envConfig = getEnvConfig();
66
78
  // Resolve contentSystemId: config first, then local store fallback (dev)
67
79
  let { contentSystemId } = config.figma;
@@ -22,4 +22,12 @@ export declare function getEnvVarSync(projectPath: string, key: string): null |
22
22
  * @param value - Environment variable value
23
23
  */
24
24
  export declare function addOrUpdateEnvVar(projectPath: string, key: string, value: string): Promise<void>;
25
+ /**
26
+ * Remove an environment variable from .env file
27
+ *
28
+ * @param projectPath - Path to project directory
29
+ * @param key - Environment variable name to remove
30
+ * @returns true if key was found and removed, false otherwise
31
+ */
32
+ export declare function removeEnvVar(projectPath: string, key: string): Promise<boolean>;
25
33
  //# sourceMappingURL=env-management.d.ts.map
@@ -115,5 +115,39 @@ import path from 'path';
115
115
  throw new Error(`Failed to update .env file: ${error instanceof Error ? error.message : 'Unknown error'}`);
116
116
  }
117
117
  }
118
+ /**
119
+ * Remove an environment variable from .env file
120
+ *
121
+ * @param projectPath - Path to project directory
122
+ * @param key - Environment variable name to remove
123
+ * @returns true if key was found and removed, false otherwise
124
+ */ export async function removeEnvVar(projectPath, key) {
125
+ const envPath = path.join(projectPath, '.env');
126
+ try {
127
+ const contents = await fs.readFile(envPath, 'utf-8');
128
+ const lines = contents.split(/\r?\n/);
129
+ const filteredLines = [];
130
+ let found = false;
131
+ for (const line of lines){
132
+ if (line.trim() && !line.trim().startsWith('#') && line.includes('=')) {
133
+ const equalsIndex = line.indexOf('=');
134
+ const lineKey = line.substring(0, equalsIndex).trim();
135
+ if (lineKey === key) {
136
+ found = true;
137
+ continue;
138
+ }
139
+ }
140
+ filteredLines.push(line);
141
+ }
142
+ if (found) {
143
+ // Collapse consecutive blank lines and trim leading/trailing blank lines
144
+ const collapsed = filteredLines.join('\n').replace(/\n{3,}/g, '\n\n').replace(/^\n+/, '');
145
+ await fs.writeFile(envPath, collapsed, 'utf-8');
146
+ }
147
+ return found;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
118
152
 
119
153
  //# sourceMappingURL=env-management.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Migrate .env file from old env var format to new format.
3
+ * Non-blocking — returns list of changes made, never throws.
4
+ */
5
+ export declare function handleEnvUpgrade(projectPath: string): Promise<string[]>;
6
+ /**
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.
10
+ */
11
+ export declare function handleUpgrade(projectPath: string): Promise<string[]>;
12
+ //# sourceMappingURL=handle-upgrade.d.ts.map
@@ -0,0 +1,242 @@
1
+ import { execSync } from 'child_process';
2
+ import fs from 'fs/promises';
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
+ /**
32
+ * Walk up from a directory to find the nearest package.json with name "@payloadcms/figma".
33
+ */ async function findPackageRoot(startDir) {
34
+ let dir = startDir;
35
+ while(true){
36
+ const pkgPath = path.join(dir, 'package.json');
37
+ try {
38
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
39
+ if (pkg.name === '@payloadcms/figma') {
40
+ return dir;
41
+ }
42
+ } catch {
43
+ // No package.json here
44
+ }
45
+ const parent = path.dirname(dir);
46
+ if (parent === dir) {
47
+ return null;
48
+ }
49
+ dir = parent;
50
+ }
51
+ }
52
+ /**
53
+ * Build and pack the local @payloadcms/figma package, returning the tgz path.
54
+ */ function packLocalPackage(pkgDir) {
55
+ execSync('pnpm build', {
56
+ cwd: pkgDir,
57
+ stdio: 'pipe'
58
+ });
59
+ const tgzOutput = execSync('pnpm pack', {
60
+ cwd: pkgDir,
61
+ stdio: 'pipe'
62
+ }).toString().trim();
63
+ const tgzName = tgzOutput.split('\n').pop();
64
+ const tgzPath = path.join(pkgDir, tgzName);
65
+ return tgzPath;
66
+ }
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
+
242
+ //# sourceMappingURL=handle-upgrade.js.map
@@ -62,4 +62,11 @@ export declare function addFigmaProperty(sourceFile: SourceFile, config: FigmaPr
62
62
  * Returns the figma object if it exists, null otherwise
63
63
  */
64
64
  export declare function readFigmaConfig(sourceFile: SourceFile): FigmaPropertyConfig | null;
65
+ /**
66
+ * Remove the contentSystemId property from figma config object.
67
+ * Used during upgrade migration — contentSystemId is now resolved from bootstrap data.
68
+ *
69
+ * @returns true if the property was found and removed
70
+ */
71
+ export declare function removeFigmaContentSystemId(sourceFile: SourceFile): boolean;
65
72
  //# sourceMappingURL=payload-config-ast.d.ts.map
@@ -472,5 +472,39 @@ import * as log from './log.js';
472
472
  useContentSystem
473
473
  };
474
474
  }
475
+ /**
476
+ * Remove the contentSystemId property from figma config object.
477
+ * Used during upgrade migration — contentSystemId is now resolved from bootstrap data.
478
+ *
479
+ * @returns true if the property was found and removed
480
+ */ export function removeFigmaContentSystemId(sourceFile) {
481
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
482
+ const buildConfigCall = callExpressions.find((ce)=>{
483
+ const expr = ce.getExpression();
484
+ const text = expr.getText();
485
+ return text === 'buildFigmaConfig' || text.endsWith('.buildFigmaConfig');
486
+ });
487
+ if (!buildConfigCall) {
488
+ return false;
489
+ }
490
+ const configArg = buildConfigCall.getArguments()[0];
491
+ if (!configArg || !Node.isObjectLiteralExpression(configArg)) {
492
+ return false;
493
+ }
494
+ const figmaProperty = configArg.getProperty('figma');
495
+ if (!figmaProperty || !Node.isPropertyAssignment(figmaProperty)) {
496
+ return false;
497
+ }
498
+ const initializer = figmaProperty.getInitializer();
499
+ if (!initializer || !Node.isObjectLiteralExpression(initializer)) {
500
+ return false;
501
+ }
502
+ const contentSystemIdProp = initializer.getProperty('contentSystemId');
503
+ if (!contentSystemIdProp) {
504
+ return false;
505
+ }
506
+ contentSystemIdProp.remove();
507
+ return true;
508
+ }
475
509
 
476
510
  //# sourceMappingURL=payload-config-ast.js.map
@@ -42,7 +42,7 @@ import * as log from './log.js';
42
42
  */ export async function installPackage(projectPath, packageName, packageManager, version = 'latest') {
43
43
  return new Promise((resolve, reject)=>{
44
44
  const command = packageManager;
45
- const packageSpec = `${packageName}@${version}`;
45
+ const packageSpec = packageName.endsWith('.tgz') ? packageName : `${packageName}@${version}`;
46
46
  const args = [
47
47
  'add',
48
48
  packageSpec
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payloadcms/figma",
3
- "version": "0.0.1-alpha.51",
3
+ "version": "0.0.1-alpha.52",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "type": "module",
6
6
  "exports": {