@payloadcms/figma 0.0.1-alpha.63 → 0.0.1-alpha.65

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 (48) hide show
  1. package/dist/auth/callback-server.d.ts +19 -7
  2. package/dist/auth/callback-server.js +72 -31
  3. package/dist/auth/crypto-utils.d.ts +11 -0
  4. package/dist/auth/crypto-utils.js +22 -1
  5. package/dist/auth/oauth-flow.d.ts +3 -1
  6. package/dist/auth/oauth-flow.js +14 -6
  7. package/dist/auth/project-token.d.ts +15 -10
  8. package/dist/auth/project-token.js +147 -64
  9. package/dist/auth/token-store-migration.js +2 -2
  10. package/dist/auth/token-store.d.ts +2 -0
  11. package/dist/auth/token-store.js +43 -3
  12. package/dist/auth/types.d.ts +11 -0
  13. package/dist/cli.js +15 -1
  14. package/dist/commands/bootstrap.d.ts +18 -0
  15. package/dist/commands/bootstrap.js +90 -0
  16. package/dist/commands/init.d.ts +4 -0
  17. package/dist/commands/init.js +76 -4
  18. package/dist/config/oauth.d.ts +2 -1
  19. package/dist/config/oauth.js +7 -1
  20. package/dist/db-content-api/generated/content-api-types.d.ts +6 -0
  21. package/dist/db-content-api/index.d.ts +2 -0
  22. package/dist/db-content-api/index.js +36 -74
  23. package/dist/lib/download-skill.d.ts +13 -0
  24. package/dist/lib/download-skill.js +79 -0
  25. package/dist/oauth/endpoints/getLoginEndpoint.js +26 -107
  26. package/dist/oauth/endpoints/getTokenLoginEndpoint.d.ts +17 -0
  27. package/dist/oauth/endpoints/getTokenLoginEndpoint.js +105 -0
  28. package/dist/oauth/index.js +8 -0
  29. package/dist/oauth/utilities/establishSession.d.ts +23 -0
  30. package/dist/oauth/utilities/establishSession.js +82 -0
  31. package/dist/oauth/utilities/exchangeCodeForAccessToken.d.ts +24 -0
  32. package/dist/oauth/utilities/exchangeCodeForAccessToken.js +28 -0
  33. package/dist/oauth/utilities/isAbsoluteURL.d.ts +2 -0
  34. package/dist/oauth/utilities/isAbsoluteURL.js +3 -0
  35. package/dist/plugin/build-config.js +65 -46
  36. package/dist/types.d.ts +2 -0
  37. package/dist/utils/download-template.d.ts +9 -1
  38. package/dist/utils/download-template.js +24 -19
  39. package/dist/utils/messages.js +9 -0
  40. package/dist/utils/parse-template-spec.d.ts +12 -0
  41. package/dist/utils/parse-template-spec.js +62 -0
  42. package/dist/utils/payload-config-modifier.js +96 -107
  43. package/dist/utils/payload-package-check.d.ts +21 -1
  44. package/dist/utils/payload-package-check.js +66 -26
  45. package/dist/utils/project.d.ts +2 -1
  46. package/dist/utils/project.js +2 -2
  47. package/package.json +9 -1
  48. package/dist/db-content-api/README.md +0 -98
@@ -131,7 +131,7 @@ function safeGet(config, key) {
131
131
  try {
132
132
  return config.get(key);
133
133
  } catch (error) {
134
- log.warning(`Migration read failed (key: ${String(key)}). ` + `File: ${config.path}. ` + `Error: ${error instanceof Error ? error.message : 'Unknown'}`);
134
+ log.debug(`Migration read failed (key: ${String(key)}). ` + `File: ${config.path}. ` + `Error: ${error instanceof Error ? error.message : 'Unknown'}`);
135
135
  return undefined;
136
136
  }
137
137
  }
@@ -148,7 +148,7 @@ function safeGet(config, key) {
148
148
  projectName: params.projectName
149
149
  });
150
150
  } catch (error) {
151
- log.warning(`Legacy token store could not be opened; skipping migration and leaving file intact. ` + `Error: ${error instanceof Error ? error.message : 'Unknown'}`);
151
+ log.debug(`Legacy token store could not be opened; skipping migration and leaving file intact. ` + `Error: ${error instanceof Error ? error.message : 'Unknown'}`);
152
152
  return null;
153
153
  }
154
154
  }
@@ -12,6 +12,7 @@ export declare function getTokenStore(environment?: Environment): TokenStore;
12
12
  */
13
13
  export declare class TokenStore {
14
14
  private baseConfigName;
15
+ private deriveLegacyKey;
15
16
  private encryptionKey;
16
17
  private environment;
17
18
  private legacyRootDir;
@@ -69,6 +70,7 @@ export declare class TokenStore {
69
70
  private getLegacyRootDir;
70
71
  private getProjectStore;
71
72
  private createConfig;
73
+ private tryMigrateLegacyKey;
72
74
  private safeGet;
73
75
  }
74
76
  //# sourceMappingURL=token-store.d.ts.map
@@ -6,7 +6,7 @@ import { TOKEN_EXPIRY_BUFFER_SECONDS } from '../config/oauth.js';
6
6
  import { getInfraEnvironment } from '../constants.js';
7
7
  import { getEnvVarSync } from '../utils/env-management.js';
8
8
  import * as log from '../utils/log.js';
9
- import { deriveEncryptionKey } from './crypto-utils.js';
9
+ import { deriveEncryptionKey, deriveLegacyEncryptionKey } from './crypto-utils.js';
10
10
  import { migrateOAuth, migrateProjectData, needsMigration } from './token-store-migration.js';
11
11
  /**
12
12
  * Environment-keyed instances for singleton pattern.
@@ -30,6 +30,7 @@ const PROJECT_STORE_SCHEMA_VERSION = 1;
30
30
  * The constructor is still exported for test isolation.
31
31
  */ export class TokenStore {
32
32
  baseConfigName;
33
+ deriveLegacyKey;
33
34
  encryptionKey;
34
35
  environment;
35
36
  legacyRootDir;
@@ -45,13 +46,14 @@ const PROJECT_STORE_SCHEMA_VERSION = 1;
45
46
  const encryptionKey = options?.encryptionKey ?? deriveEncryptionKey();
46
47
  const baseConfigName = options?.configName || projectName;
47
48
  this.baseConfigName = baseConfigName;
49
+ this.deriveLegacyKey = options?.deriveLegacyKey ?? deriveLegacyEncryptionKey;
48
50
  this.encryptionKey = encryptionKey;
49
51
  this.environment = environment;
50
52
  this.projectName = projectName;
51
53
  // Resolve the legacy root dir (matching Conf's env-paths convention)
52
54
  // without instantiating Conf, which would read/parse the encrypted file
53
55
  // and could wipe it under clearInvalidConfig.
54
- this.legacyRootDir = envPaths(projectName).config;
56
+ this.legacyRootDir = options?.rootDir ?? envPaths(projectName).config;
55
57
  const legacyFilePath = path.join(this.legacyRootDir, `${baseConfigName}.json`);
56
58
  const newOAuthCwd = this.buildOAuthStoreCwd();
57
59
  const newOAuthFilePath = path.join(newOAuthCwd, `${this.buildOAuthConfigName()}.json`);
@@ -84,7 +86,7 @@ const PROJECT_STORE_SCHEMA_VERSION = 1;
84
86
  if (environment !== 'production') {
85
87
  const oldEnvProjectName = `payloadcms-figma-${environment}`;
86
88
  const oldEnvConfigName = options?.configName || oldEnvProjectName;
87
- const oldEnvLegacyDir = envPaths(oldEnvProjectName).config;
89
+ const oldEnvLegacyDir = options?.oldEnvLegacyDir ?? envPaths(oldEnvProjectName).config;
88
90
  const oldEnvLegacyFilePath = path.join(oldEnvLegacyDir, `${oldEnvConfigName}.json`);
89
91
  if (needsMigration({
90
92
  legacyFilePath: oldEnvLegacyFilePath,
@@ -396,6 +398,13 @@ const PROJECT_STORE_SCHEMA_VERSION = 1;
396
398
  try {
397
399
  return new Conf(confOptions);
398
400
  } catch (error) {
401
+ const migrated = this.tryMigrateLegacyKey(confOptions);
402
+ if (migrated) {
403
+ if (this.environment === 'staging') {
404
+ log.info('Successfully migrated local token cache.');
405
+ }
406
+ return migrated;
407
+ }
399
408
  // Deserialization failed (e.g. encryption key changed).
400
409
  // Log diagnostics, then clear and replace with a fresh store.
401
410
  if (this.environment === 'staging') {
@@ -412,6 +421,37 @@ const PROJECT_STORE_SCHEMA_VERSION = 1;
412
421
  return config;
413
422
  }
414
423
  }
424
+ tryMigrateLegacyKey(confOptions) {
425
+ if (!confOptions.encryptionKey) {
426
+ return null;
427
+ }
428
+ let snapshot;
429
+ try {
430
+ const legacyConf = new Conf({
431
+ ...confOptions,
432
+ clearInvalidConfig: false,
433
+ encryptionKey: this.deriveLegacyKey()
434
+ });
435
+ snapshot = {
436
+ ...legacyConf.store
437
+ };
438
+ } catch {
439
+ return null;
440
+ }
441
+ try {
442
+ const rewritten = new Conf({
443
+ ...confOptions,
444
+ clearInvalidConfig: true
445
+ });
446
+ rewritten.clear();
447
+ for (const [key, value] of Object.entries(snapshot)){
448
+ rewritten.set(key, value);
449
+ }
450
+ return rewritten;
451
+ } catch {
452
+ return null;
453
+ }
454
+ }
415
455
  safeGet(params) {
416
456
  const { config, key } = params;
417
457
  try {
@@ -109,10 +109,21 @@ export interface OAuthTokenRefreshParams {
109
109
  export type TokenStoreConfig = {
110
110
  /** Config file name (for testing) */
111
111
  configName?: string;
112
+ /**
113
+ * Override for the legacy key derivation used by transparent migration.
114
+ * Tests pass an explicit function so they don't depend on the host machine's
115
+ * actual entropy. Production code leaves this undefined and the constructor
116
+ * falls back to `deriveLegacyEncryptionKey`.
117
+ */
118
+ deriveLegacyKey?: () => string;
112
119
  /** Optional override for storage encryption key (primarily for tests). */
113
120
  encryptionKey?: string;
114
121
  /** Environment for config file naming */
115
122
  environment?: Environment;
123
+ /** Override for the old per-environment legacy directory (primarily for tests). */
124
+ oldEnvLegacyDir?: string;
125
+ /** Override for the storage root directory (primarily for tests). */
126
+ rootDir?: string;
116
127
  };
117
128
  /**
118
129
  * PKCE (Proof Key for Code Exchange) pair for OAuth2 public clients
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
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';
@@ -42,9 +43,12 @@ class Main {
42
43
  '--help': Boolean,
43
44
  '--id': String,
44
45
  '--infra-env': String,
46
+ '--json': Boolean,
45
47
  '--name': String,
48
+ '--no-skill': Boolean,
46
49
  '--skip-auth': Boolean,
47
50
  '--skip-build': Boolean,
51
+ '--template': String,
48
52
  '--version': Boolean,
49
53
  '--yes': Boolean,
50
54
  // Aliases
@@ -53,6 +57,7 @@ class Main {
53
57
  '-f': '--force',
54
58
  '-h': '--help',
55
59
  '-n': '--name',
60
+ '-t': '--template',
56
61
  '-v': '--version',
57
62
  '-y': '--yes'
58
63
  }, {
@@ -112,6 +117,13 @@ class Main {
112
117
  p.intro(pc.bgCyan(pc.black(' @payloadcms/figma ')));
113
118
  // Route to appropriate command handler
114
119
  switch(subcommand){
120
+ case 'bootstrap':
121
+ await bootstrapCommand({
122
+ id: this.args['--id'],
123
+ env: this.args['--env'],
124
+ json: this.args['--json']
125
+ });
126
+ break;
115
127
  case 'build-lambda-zip':
116
128
  await buildLambdaZipCommand();
117
129
  break;
@@ -137,7 +149,9 @@ class Main {
137
149
  debug: this.args['--debug'],
138
150
  env: this.args['--env'],
139
151
  force: this.args['--force'],
140
- skipAuth: this.args['--skip-auth']
152
+ noSkill: this.args['--no-skill'],
153
+ skipAuth: this.args['--skip-auth'],
154
+ template: this.args['--template']
141
155
  });
142
156
  break;
143
157
  case 'list-tokens':
@@ -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
@@ -12,8 +12,12 @@ export interface InitCommandOptions {
12
12
  id?: string;
13
13
  /** Project name (for scaffolding) */
14
14
  name?: string;
15
+ /** Skip Payload skill installation */
16
+ noSkill?: boolean;
15
17
  /** Skip authentication check (for testing/development) */
16
18
  skipAuth?: boolean;
19
+ /** GitHub template override, e.g. "v3.80.0:website" or "owner/repo#ref:template-path" */
20
+ template?: string;
17
21
  /** Skip prompts and use defaults where possible */
18
22
  yes?: boolean;
19
23
  }
@@ -8,6 +8,7 @@ import { tryGetCredential } from '../auth/oauth-flow.js';
8
8
  import { getValidProjectToken, ProjectTokenError } from '../auth/project-token.js';
9
9
  import { getTokenStore } from '../auth/token-store.js';
10
10
  import { getInfraEnvironment, getProjectNotFoundMessage } from '../constants.js';
11
+ import { downloadSkill } from '../lib/download-skill.js';
11
12
  import { ensureGitignore } from '../utils/config.js';
12
13
  import { addOrUpdateEnvVar } from '../utils/env-management.js';
13
14
  import { isDebug } from '../utils/is-debug.js';
@@ -21,6 +22,23 @@ import { resolveEnvironment } from '../utils/resolve-environment.js';
21
22
  import { checkForUpdates, getOwnVersion } from '../utils/version-check.js';
22
23
  import { loginCommand } from './login.js';
23
24
  import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
25
+ import { DEFAULT_TEMPLATE_SOURCE } from '../utils/download-template.js';
26
+ import { parseTemplateSpec, TemplateSpecParseError } from '../utils/parse-template-spec.js';
27
+ async function maybeInstallPayloadSkill(args) {
28
+ if (args.noSkill) {
29
+ return;
30
+ }
31
+ const result = await downloadSkill({
32
+ projectDir: args.projectDir
33
+ });
34
+ if (result.ok) {
35
+ return;
36
+ }
37
+ if (result.reason === 'already-installed') {
38
+ return;
39
+ }
40
+ p.log.warn(pc.yellow(`⚠ Could not install Payload skill (${result.reason}${result.detail ? `: ${result.detail}` : ''}). Continuing.`));
41
+ }
24
42
  /**
25
43
  * Generate and store project token for a content system
26
44
  * Non-blocking - will show warning but not exit on failure
@@ -111,6 +129,18 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
111
129
  process.exit(1);
112
130
  }
113
131
  const cmsResourceId = options.id;
132
+ // Parse --template spec early so malformed values abort before any I/O
133
+ let templateSource;
134
+ if (options.template) {
135
+ try {
136
+ templateSource = parseTemplateSpec(options.template);
137
+ } catch (error) {
138
+ const message = error instanceof TemplateSpecParseError ? error.message : 'Invalid --template value';
139
+ p.log.error(pc.red(`✗ ${message}`));
140
+ p.note('Format: [<owner>/<repo>#]<ref>:<template-path>\n' + 'Examples:\n' + ' blank\n' + ' v3.80.0:website\n' + ' myorg/payload-fork#feat-x:templates/custom', 'Template spec');
141
+ process.exit(1);
142
+ }
143
+ }
114
144
  // Fetch bootstrap info (oauth creds + environments)
115
145
  let bootstrapInfo = null;
116
146
  let resolvedEnv = null;
@@ -225,6 +255,18 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
225
255
  p.note('Run this command in a directory without an existing project or in an existing Payload project.', 'Action Required');
226
256
  process.exit(1);
227
257
  }
258
+ // npm leaves node_modules in a transient state after a sequence of
259
+ // add/remove calls; without a final `install` the subprocess that runs
260
+ // generate:importmap silently misses @payloadcms/figma subpath components.
261
+ s.start('Reconciling dependencies...');
262
+ try {
263
+ await installDependencies(process.cwd(), packageManager);
264
+ s.stop(pc.green('✓ Dependencies reconciled'));
265
+ } catch (error) {
266
+ s.stop(pc.red('✗ Failed to reconcile dependencies before generate:importmap'));
267
+ log.error(error instanceof Error ? error.message : 'Unknown error');
268
+ process.exit(1);
269
+ }
228
270
  // Generate import map to prevent errors on first dev run
229
271
  const importMapResult = await runScript(process.cwd(), 'generate:importmap', packageManager);
230
272
  if (!importMapResult) {
@@ -238,9 +280,16 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
238
280
  '.env.local',
239
281
  'lambda.zip'
240
282
  ]);
283
+ await maybeInstallPayloadSkill({
284
+ noSkill: options.noSkill,
285
+ projectDir: process.cwd()
286
+ });
241
287
  // Generate project token (unless skipping auth)
242
288
  if (!options.skipAuth && resolvedEnv) {
243
- await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s);
289
+ await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s, {
290
+ environmentName: resolvedEnv.name,
291
+ projectId: cmsResourceId
292
+ });
244
293
  }
245
294
  // Success message for existing project
246
295
  p.outro(pc.green('✓ Project initialized successfully!'));
@@ -286,9 +335,13 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
286
335
  process.exit(1);
287
336
  }
288
337
  // Scaffold project
289
- s.start('Downloading template from GitHub...');
338
+ const resolvedSource = {
339
+ ...DEFAULT_TEMPLATE_SOURCE,
340
+ ...templateSource
341
+ };
342
+ s.start(`Downloading template "${resolvedSource.templatePath}" from ` + `${resolvedSource.owner}/${resolvedSource.repo}@${resolvedSource.ref}...`);
290
343
  try {
291
- await scaffoldProject(fullPath, projectName, packageManager);
344
+ await scaffoldProject(fullPath, projectName, packageManager, templateSource);
292
345
  s.stop(pc.green('✓ Template downloaded'));
293
346
  s.start('Installing dependencies...');
294
347
  await installDependencies(fullPath, packageManager);
@@ -339,14 +392,33 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
339
392
  } catch (error) {
340
393
  p.log.warn(pc.yellow(`⚠ Failed to update .env file: ${error instanceof Error ? error.message : 'Unknown error'}`));
341
394
  }
395
+ // npm leaves node_modules in a transient state after a sequence of
396
+ // add/remove calls; without a final `install` the subprocess that runs
397
+ // generate:importmap silently misses @payloadcms/figma subpath components.
398
+ s.start('Reconciling dependencies...');
399
+ try {
400
+ await installDependencies(fullPath, packageManager);
401
+ s.stop(pc.green('✓ Dependencies reconciled'));
402
+ } catch (error) {
403
+ s.stop(pc.red('✗ Failed to reconcile dependencies before generate:importmap'));
404
+ log.error(error instanceof Error ? error.message : 'Unknown error');
405
+ process.exit(1);
406
+ }
342
407
  // Generate import map to prevent errors on first dev run
343
408
  const importMapResult = await runScript(fullPath, 'generate:importmap', packageManager);
344
409
  if (!importMapResult) {
345
410
  p.log.warn(pc.yellow('⚠ Import map generation failed — it will be generated on first dev run'));
346
411
  }
412
+ await maybeInstallPayloadSkill({
413
+ noSkill: options.noSkill,
414
+ projectDir: fullPath
415
+ });
347
416
  // Generate project token (unless skipping auth)
348
417
  if (!options.skipAuth && resolvedEnv) {
349
- await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s);
418
+ await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s, {
419
+ environmentName: resolvedEnv.name,
420
+ projectId: cmsResourceId
421
+ });
350
422
  }
351
423
  // Initialize git repository (after all files including lock file are ready)
352
424
  initializeGitRepo(fullPath);
@@ -1,5 +1,6 @@
1
1
  import type { OAuthConfig } from '../auth/types.js';
2
- export declare const DEFAULT_CALLBACK_PORT = 34462;
2
+ export declare const DEFAULT_CALLBACK_PORTS: number[];
3
+ export declare const DEFAULT_CALLBACK_PORT: number;
3
4
  /**
4
5
  * Get OAuth configuration for current environment.
5
6
  *
@@ -1,5 +1,11 @@
1
1
  import { getEnvConfig, getInfraEnvironment } from '../constants.js';
2
- export const DEFAULT_CALLBACK_PORT = 34462;
2
+ export const DEFAULT_CALLBACK_PORTS = [
3
+ 34462,
4
+ 34463,
5
+ 34464,
6
+ 34465
7
+ ];
8
+ export const DEFAULT_CALLBACK_PORT = DEFAULT_CALLBACK_PORTS[0];
3
9
  /**
4
10
  * Get OAuth configuration for current environment.
5
11
  *
@@ -3483,6 +3483,8 @@ export type components = {
3483
3483
  CreateDocumentRequest: {
3484
3484
  /** @example cms-xxxxx-xxxxx */
3485
3485
  contentSystemId: string;
3486
+ /** User-defined Payload environment name (e.g. "production"). */
3487
+ environmentName?: string;
3486
3488
  /** @example posts */
3487
3489
  collection: string;
3488
3490
  doc: components['schemas']['DocumentData'];
@@ -3594,6 +3596,8 @@ export type components = {
3594
3596
  UpdateDocumentRequest: {
3595
3597
  /** @example cms-xxxxx-xxxxx */
3596
3598
  contentSystemId: string;
3599
+ /** User-defined Payload environment name (e.g. "production"). */
3600
+ environmentName?: string;
3597
3601
  /** @example posts */
3598
3602
  collection: string;
3599
3603
  /** @example false */
@@ -3627,6 +3631,8 @@ export type components = {
3627
3631
  DeleteDocumentRequest: {
3628
3632
  /** @example cms-xxxxx-xxxxx */
3629
3633
  contentSystemId: string;
3634
+ /** User-defined Payload environment name (e.g. "production"). */
3635
+ environmentName?: string;
3630
3636
  /** @example posts */
3631
3637
  collection: string;
3632
3638
  locale?: components['schemas']['LocaleClause'];
@@ -6,6 +6,7 @@ type ContentAPIOptions = {
6
6
  allowIDOnCreate?: boolean;
7
7
  auth: AuthMode;
8
8
  contentSystemId: string;
9
+ environmentName?: string;
9
10
  url: string;
10
11
  };
11
12
  export type ContentAPIAdapter = {
@@ -13,6 +14,7 @@ export type ContentAPIAdapter = {
13
14
  clearDatabase: () => Promise<void>;
14
15
  client: ReturnType<typeof createClient<paths>>;
15
16
  contentSystemId: string;
17
+ environmentName?: string;
16
18
  idType: 'uuid';
17
19
  url: string;
18
20
  } & BaseDatabaseAdapter;