@payloadcms/figma 0.1.0-alpha.3 → 0.1.0-alpha.4

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 (78) hide show
  1. package/dist/api/check-project-subdomain-availability.d.ts +12 -0
  2. package/dist/api/check-project-subdomain-availability.js +32 -0
  3. package/dist/api/control-plane-error.d.ts +6 -0
  4. package/dist/api/control-plane-error.js +8 -0
  5. package/dist/api/control-plane-fetch.d.ts +7 -0
  6. package/dist/api/control-plane-fetch.js +37 -0
  7. package/dist/api/control-plane.d.ts +0 -8
  8. package/dist/api/control-plane.js +1 -47
  9. package/dist/api/create-project.d.ts +18 -0
  10. package/dist/api/create-project.js +37 -0
  11. package/dist/api/list-project-workspaces.d.ts +8 -0
  12. package/dist/api/list-project-workspaces.js +28 -0
  13. package/dist/api/project-resource-type.d.ts +1 -0
  14. package/dist/api/project-resource-type.js +1 -0
  15. package/dist/auth/headless-oauth-flow.d.ts +23 -0
  16. package/dist/auth/headless-oauth-flow.js +69 -0
  17. package/dist/auth/oauth-flow.d.ts +2 -0
  18. package/dist/auth/oauth-flow.js +13 -8
  19. package/dist/auth/token-store.d.ts +4 -1
  20. package/dist/auth/token-store.js +14 -0
  21. package/dist/auth/types.d.ts +12 -0
  22. package/dist/cli.d.ts +10 -1
  23. package/dist/cli.js +39 -4
  24. package/dist/commands/bootstrap.js +2 -1
  25. package/dist/commands/deploy.d.ts +10 -0
  26. package/dist/commands/deploy.js +39 -11
  27. package/dist/commands/detect-project-resource-type.d.ts +3 -0
  28. package/dist/commands/detect-project-resource-type.js +28 -0
  29. package/dist/commands/init.d.ts +2 -2
  30. package/dist/commands/init.js +163 -78
  31. package/dist/commands/login.d.ts +11 -1
  32. package/dist/commands/login.js +66 -4
  33. package/dist/commands/prompt-to-create-project/format-project-subdomain.d.ts +1 -0
  34. package/dist/commands/prompt-to-create-project/format-project-subdomain.js +3 -0
  35. package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.d.ts +1 -0
  36. package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.js +7 -0
  37. package/dist/commands/prompt-to-create-project/get-default-project-name.d.ts +1 -0
  38. package/dist/commands/prompt-to-create-project/get-default-project-name.js +13 -0
  39. package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.d.ts +7 -0
  40. package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.js +32 -0
  41. package/dist/commands/prompt-to-create-project/resolve-project-region.d.ts +8 -0
  42. package/dist/commands/prompt-to-create-project/resolve-project-region.js +27 -0
  43. package/dist/commands/prompt-to-create-project/resolve-project-subdomain.d.ts +9 -0
  44. package/dist/commands/prompt-to-create-project/resolve-project-subdomain.js +54 -0
  45. package/dist/commands/prompt-to-create-project/resolve-text-input.d.ts +9 -0
  46. package/dist/commands/prompt-to-create-project/resolve-text-input.js +23 -0
  47. package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.d.ts +12 -0
  48. package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.js +36 -0
  49. package/dist/commands/prompt-to-create-project/validate-subdomain.d.ts +1 -0
  50. package/dist/commands/prompt-to-create-project/validate-subdomain.js +9 -0
  51. package/dist/commands/prompt-to-create-project.d.ts +24 -0
  52. package/dist/commands/prompt-to-create-project.js +69 -0
  53. package/dist/commands/setup-agent.d.ts +12 -0
  54. package/dist/commands/setup-agent.js +123 -0
  55. package/dist/commands/write-project-configuration.d.ts +12 -0
  56. package/dist/commands/write-project-configuration.js +16 -0
  57. package/dist/db-content-api/index.js +21 -16
  58. package/dist/db-content-api/utilities/data/injectGlobalType.d.ts +6 -0
  59. package/dist/db-content-api/utilities/data/injectGlobalType.js +11 -0
  60. package/dist/db-content-api/utilities/data/stringifyNumericIds.d.ts +1 -0
  61. package/dist/db-content-api/utilities/data/stringifyNumericIds.js +19 -0
  62. package/dist/db-content-api/utilities/isRecord.d.ts +9 -0
  63. package/dist/db-content-api/utilities/isRecord.js +19 -0
  64. package/dist/db-content-api/utilities/where.js +6 -4
  65. package/dist/lib/write-agent-config.d.ts +12 -0
  66. package/dist/lib/write-agent-config.js +52 -0
  67. package/dist/skills/deploy-to-figma/SKILL.md +234 -0
  68. package/dist/skills/deploy-to-figma/references/cli-reference.md +146 -0
  69. package/dist/types.d.ts +9 -0
  70. package/dist/utils/lambda-config.js +4 -14
  71. package/dist/utils/messages.js +22 -3
  72. package/dist/utils/payload-generate.d.ts +10 -0
  73. package/dist/utils/payload-generate.js +59 -0
  74. package/dist/utils/pnpm-builds.d.ts +14 -0
  75. package/dist/utils/pnpm-builds.js +125 -0
  76. package/package.json +2 -1
  77. package/dist/lib/download-skill.d.ts +0 -12
  78. package/dist/lib/download-skill.js +0 -77
@@ -7,16 +7,26 @@ export interface DeployCommandOptions {
7
7
  adapter?: AdapterName;
8
8
  /** Enable debug mode */
9
9
  debug?: boolean;
10
+ /** Description used when creating an uninitialized project */
11
+ description?: string;
10
12
  /** Figma environment name (e.g. 'production', 'staging') */
11
13
  env?: string;
12
14
  /** CMS Resource ID (overrides FIGMA_PROJECT_ID) */
13
15
  id?: string;
16
+ /** Name used when creating an uninitialized project */
17
+ name?: string;
14
18
  /** Exact framework build output root */
15
19
  output?: string;
20
+ /** Region used when creating an uninitialized project */
21
+ region?: string;
16
22
  /** Skip the build step (use existing build) */
17
23
  skipBuild?: boolean;
18
24
  /** Skip the client-side unzipped bundle size pre-flight check */
19
25
  skipSizeCheck?: boolean;
26
+ /** Subdomain used when creating an uninitialized project */
27
+ subdomain?: string;
28
+ /** Workspace plan key used when creating an uninitialized project */
29
+ workspace?: string;
20
30
  /** Skip confirmation prompts */
21
31
  yes?: boolean;
22
32
  }
@@ -2,7 +2,8 @@ import * as p from '@clack/prompts';
2
2
  import spawn from 'cross-spawn';
3
3
  import path from 'path';
4
4
  import pc from 'picocolors';
5
- import { ControlPlaneError, createDeployment, getBootstrapInfo, performDeployment } from '../api/control-plane.js';
5
+ import { createDeployment, getBootstrapInfo, performDeployment } from '../api/control-plane.js';
6
+ import { ControlPlaneError } from '../api/control-plane-error.js';
6
7
  import { tryGetCredential } from '../auth/oauth-flow.js';
7
8
  import { getTokenStore } from '../auth/token-store.js';
8
9
  import { getInfraEnvironment, getProjectNotFoundMessage } from '../constants.js';
@@ -16,7 +17,10 @@ import { getPackageManager } from '../utils/package-manager.js';
16
17
  import { isInProjectDirectory } from '../utils/project.js';
17
18
  import { resolveEnvironment } from '../utils/resolve-environment.js';
18
19
  import { uploadLambdaZip, uploadStaticAssets } from '../utils/s3-upload.js';
20
+ import { detectProjectResourceType } from './detect-project-resource-type.js';
19
21
  import { loginCommand } from './login.js';
22
+ import { promptToCreateProject } from './prompt-to-create-project.js';
23
+ import { writeProjectConfiguration } from './write-project-configuration.js';
20
24
  /**
21
25
  * Format bytes to human-readable string
22
26
  */ function formatBytes(bytes) {
@@ -52,15 +56,6 @@ import { loginCommand } from './login.js';
52
56
  */ export async function deployCommand(options) {
53
57
  const projectPath = process.cwd();
54
58
  const spinner = p.spinner();
55
- // Resolve project ID
56
- let projectId = options.id;
57
- if (!projectId) {
58
- projectId = await getEnvVar(projectPath, 'FIGMA_PROJECT_ID') ?? undefined;
59
- }
60
- if (!projectId) {
61
- p.log.error(pc.red('✗ Project ID not found. Run `@payloadcms/figma init` first, or use --id.'));
62
- process.exit(1);
63
- }
64
59
  // Resolve environment name
65
60
  const environmentName = options.env ?? await getEnvVar(projectPath, 'FIGMA_ENVIRONMENT_NAME') ?? undefined;
66
61
  try {
@@ -76,13 +71,38 @@ import { loginCommand } from './login.js';
76
71
  let credential = await tryGetCredential(tokenStore);
77
72
  if (!credential) {
78
73
  p.log.message('Please log in to continue');
79
- await loginCommand();
74
+ await loginCommand({
75
+ openBrowserWithoutPrompt: options.yes
76
+ });
80
77
  credential = await tryGetCredential(tokenStore);
81
78
  if (!credential) {
82
79
  p.log.error(pc.red('✗ Authentication failed'));
83
80
  process.exit(1);
84
81
  }
85
82
  }
83
+ // Resolve or create the CMS project.
84
+ let projectId = options.id;
85
+ if (!projectId) {
86
+ projectId = await getEnvVar(projectPath, 'FIGMA_PROJECT_ID') ?? undefined;
87
+ }
88
+ const isProjectCreated = !projectId;
89
+ if (!projectId) {
90
+ const resourceType = options.adapter === 'vite' ? 'vite' : options.adapter ? 'payload' : await detectProjectResourceType(projectPath);
91
+ const project = await promptToCreateProject({
92
+ credential,
93
+ inputs: {
94
+ name: options.name,
95
+ description: options.description,
96
+ region: options.region,
97
+ subdomain: options.subdomain,
98
+ workspacePlanKey: options.workspace
99
+ },
100
+ nonInteractive: options.yes,
101
+ projectPath,
102
+ resourceType
103
+ });
104
+ projectId = project.id;
105
+ }
86
106
  // Resolve tenant instance ID via bootstrap
87
107
  spinner.start('Resolving deployment target...');
88
108
  let tenantInstanceId;
@@ -93,6 +113,14 @@ import { loginCommand } from './login.js';
93
113
  environments: bootstrapInfo.environments
94
114
  });
95
115
  tenantInstanceId = resolvedEnv.tenantInstanceId;
116
+ await writeProjectConfiguration({
117
+ bootstrapInfo,
118
+ environmentName: resolvedEnv.name,
119
+ projectDir: projectPath,
120
+ projectId,
121
+ shouldWriteEnvironmentVariables: isProjectCreated,
122
+ tokenStore
123
+ });
96
124
  spinner.stop(pc.green(`✓ Target: ${resolvedEnv.name}`));
97
125
  } catch (error) {
98
126
  if (error instanceof ControlPlaneError && (error.statusCode === 404 || error.statusCode === 410)) {
@@ -0,0 +1,3 @@
1
+ import type { ProjectResourceType } from '../api/project-resource-type.js';
2
+ /** Detect whether project provisioning should use static Vite or Payload infrastructure. */
3
+ export declare function detectProjectResourceType(projectPath?: string): Promise<ProjectResourceType>;
@@ -0,0 +1,28 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ /** Detect whether project provisioning should use static Vite or Payload infrastructure. */ export async function detectProjectResourceType(projectPath = process.cwd()) {
4
+ try {
5
+ const content = await fs.readFile(path.join(projectPath, 'package.json'), 'utf-8');
6
+ const packageJson = JSON.parse(content);
7
+ const dependencies = {
8
+ ...packageJson.dependencies,
9
+ ...packageJson.devDependencies
10
+ };
11
+ if (dependencies.payload) {
12
+ return 'payload';
13
+ }
14
+ if (dependencies.vite || Object.values(packageJson.scripts ?? {}).some((script)=>/\bvite\b/.test(script))) {
15
+ return 'vite';
16
+ }
17
+ } catch (error) {
18
+ if (error.code !== 'ENOENT') {
19
+ return 'payload';
20
+ }
21
+ }
22
+ try {
23
+ await fs.access(path.join(projectPath, 'index.html'));
24
+ return 'vite';
25
+ } catch {
26
+ return 'payload';
27
+ }
28
+ }
@@ -12,8 +12,8 @@ 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
+ /** Skip writing AI agent config files (AGENTS.md, CLAUDE.md) */
16
+ noAgent?: boolean;
17
17
  /** npm dist-tag or explicit version for core payload packages (e.g. 'canary', 'latest', '4.0.0-canary.16'); defaults to 'canary' */
18
18
  payloadVersion?: string;
19
19
  /** Skip authentication check (for testing/development) */
@@ -2,13 +2,14 @@ import * as p from '@clack/prompts';
2
2
  import fs from 'fs/promises';
3
3
  import path from 'path';
4
4
  import pc from 'picocolors';
5
- import { ControlPlaneError, getBootstrapInfo } from '../api/control-plane.js';
5
+ import { getBootstrapInfo } from '../api/control-plane.js';
6
+ import { ControlPlaneError } from '../api/control-plane-error.js';
6
7
  import { FigmaApiError } from '../api/figma-api.js';
7
8
  import { tryGetCredential } from '../auth/oauth-flow.js';
8
9
  import { getValidProjectToken, ProjectTokenError } from '../auth/project-token.js';
9
10
  import { getTokenStore } from '../auth/token-store.js';
10
11
  import { getInfraEnvironment, getProjectNotFoundMessage } from '../constants.js';
11
- import { downloadSkill } from '../lib/download-skill.js';
12
+ import { writeAgentConfigFiles } from '../lib/write-agent-config.js';
12
13
  import { ensureGitignore } from '../utils/config.js';
13
14
  import { addOrUpdateEnvVar } from '../utils/env-management.js';
14
15
  import { isDebug } from '../utils/is-debug.js';
@@ -16,27 +17,54 @@ import { applyLambdaModifications } from '../utils/lambda-config.js';
16
17
  import * as log from '../utils/log.js';
17
18
  import { detectPackageManagerFromEnvironment, getRunCommand, resolvePackageManager } from '../utils/package-manager.js';
18
19
  import { displayManualInstructions, ensurePayloadFigmaConfig } from '../utils/payload-config-modifier.js';
20
+ import { generateImportMap, generateTypes } from '../utils/payload-generate.js';
21
+ import { ensurePnpmWorkspace } from '../utils/pnpm-builds.js';
19
22
  import { detectPayloadProject, hasRequiredDependencies, initializeGitRepo, installDependencies, isInProjectDirectory, scaffoldProject, validatePayloadVersion } from '../utils/project.js';
20
23
  import { resolveEnvironment } from '../utils/resolve-environment.js';
21
24
  import { checkForUpdates, getOwnVersion } from '../utils/version-check.js';
25
+ import { detectProjectResourceType } from './detect-project-resource-type.js';
22
26
  import { loginCommand } from './login.js';
27
+ import { promptToCreateProject } from './prompt-to-create-project.js';
28
+ import { writeProjectConfiguration } from './write-project-configuration.js';
23
29
  import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
24
30
  import { DEFAULT_TEMPLATE_SOURCE } from '../utils/download-template.js';
25
31
  import { parseTemplateSpec, TemplateSpecParseError } from '../utils/parse-template-spec.js';
26
- async function maybeInstallPayloadSkill(args) {
27
- if (args.noSkill) {
32
+ async function maybeWriteAgentConfigFiles(args) {
33
+ if (args.noAgent) {
28
34
  return;
29
35
  }
30
- const result = await downloadSkill({
31
- projectDir: args.projectDir
36
+ try {
37
+ await writeAgentConfigFiles({
38
+ projectDir: args.projectDir
39
+ });
40
+ } catch (err) {
41
+ p.log.warn(pc.yellow(`⚠ Could not write AI agent config (${err instanceof Error ? err.message : String(err)}). Continuing.`));
42
+ }
43
+ }
44
+ /** Generate the admin import map and Payload types so the scaffold builds clean. */ async function runProjectCodegen(args) {
45
+ await runPayloadGenerate({
46
+ ...args,
47
+ artifact: 'import map',
48
+ command: 'generate:importmap',
49
+ generate: generateImportMap
32
50
  });
51
+ await runPayloadGenerate({
52
+ ...args,
53
+ artifact: 'types',
54
+ command: 'generate:types',
55
+ generate: generateTypes
56
+ });
57
+ }
58
+ /** Non-fatal: on failure, warn with the command to run later. */ async function runPayloadGenerate(args) {
59
+ args.spinner.start(`Generating ${args.artifact}...`);
60
+ const result = await args.generate(args.projectDir);
61
+ const capitalized = args.artifact.charAt(0).toUpperCase() + args.artifact.slice(1);
33
62
  if (result.ok) {
63
+ args.spinner.stop(pc.green(`✓ ${capitalized} generated`));
34
64
  return;
35
65
  }
36
- if (result.reason === 'already-installed') {
37
- return;
38
- }
39
- p.log.warn(pc.yellow(`⚠ Could not install Payload skill (${result.reason}${result.detail ? `: ${result.detail}` : ''}). Continuing.`));
66
+ args.spinner.stop(pc.yellow(`⚠ Could not generate ${args.artifact}`));
67
+ p.log.warn(pc.yellow(`Run '${getRunCommand(args.packageManager)} ${args.command}' later. ${result.error}`));
40
68
  }
41
69
  /**
42
70
  * Generate and store project token for a content system
@@ -94,6 +122,7 @@ async function maybeInstallPayloadSkill(args) {
94
122
  // Check authentication
95
123
  const tokenStore = getTokenStore();
96
124
  const s = p.spinner();
125
+ let credential = null;
97
126
  if (options.skipAuth) {
98
127
  // Skip authentication for testing/development
99
128
  p.log.warn(pc.yellow('Skipping authentication (--skip-auth mode)'));
@@ -102,14 +131,14 @@ async function maybeInstallPayloadSkill(args) {
102
131
  if (!tokenStore.hasValidOauthToken()) {
103
132
  // Need to refresh or authenticate - show spinner for API call
104
133
  s.start('Checking authentication...');
105
- const credential = await tryGetCredential(tokenStore);
134
+ credential = await tryGetCredential(tokenStore);
106
135
  if (!credential) {
107
136
  s.stop('Authentication required to continue');
108
137
  await loginCommand({
109
138
  showNextSteps: false
110
139
  });
111
- const newCredential = await tryGetCredential(tokenStore);
112
- if (!newCredential) {
140
+ credential = await tryGetCredential(tokenStore);
141
+ if (!credential) {
113
142
  p.log.error('Authentication failed');
114
143
  process.exit(1);
115
144
  }
@@ -118,16 +147,12 @@ async function maybeInstallPayloadSkill(args) {
118
147
  }
119
148
  }
120
149
  }
150
+ if (!options.skipAuth && !credential) {
151
+ credential = await tryGetCredential(tokenStore);
152
+ }
121
153
  // Note: We no longer check for figma.config.json here
122
154
  // Instead, the AST detection will check if figma object already exists in payload.config.ts
123
155
  // and skip modification if it does (unless --force is used)
124
- // Step 3: Get CMS Resource ID (required argument)
125
- log.debug('Configuring Project...');
126
- if (!options.id) {
127
- p.log.error('CMS Resource ID is required. Use --id <cms-resource-id>');
128
- process.exit(1);
129
- }
130
- const cmsResourceId = options.id;
131
156
  // Parse --template spec early so malformed values abort before any I/O
132
157
  let templateSource;
133
158
  if (options.template) {
@@ -140,13 +165,95 @@ async function maybeInstallPayloadSkill(args) {
140
165
  process.exit(1);
141
166
  }
142
167
  }
168
+ // Step 3: Resolve or create the CMS project
169
+ log.debug('Configuring Project...');
170
+ const hasPackageJson = await isInProjectDirectory();
171
+ const resourceType = await detectProjectResourceType();
172
+ const isInProject = hasPackageJson || resourceType === 'vite';
173
+ let projectDirectory = process.cwd();
174
+ if (isInProject && resourceType !== 'vite' && !options.id) {
175
+ const projectInfo = await detectPayloadProject();
176
+ if (!projectInfo.hasPayload) {
177
+ p.log.warn(pc.yellow('⚠ Payload was not detected in this project'));
178
+ p.note('Run this command in a directory without an existing project or in an existing Payload project.', 'Action Required');
179
+ process.exit(1);
180
+ }
181
+ if (projectInfo.payloadVersion && !validatePayloadVersion(projectInfo.payloadVersion)) {
182
+ p.log.warn(pc.yellow(`Payload version ${projectInfo.payloadVersion} detected. Version 4.x or newer is required.`));
183
+ p.note('Upgrade to Payload 4.x or newer before continuing', 'Action Required');
184
+ process.exit(1);
185
+ }
186
+ }
187
+ if (!isInProject) {
188
+ let projectPath;
189
+ if (options.name) {
190
+ projectPath = options.name;
191
+ } else {
192
+ const projectPathInput = await p.text({
193
+ initialValue: './',
194
+ message: 'Enter path to create project:',
195
+ placeholder: './my-cms-project',
196
+ validate: (value)=>{
197
+ if (!value) {
198
+ return 'Path is required';
199
+ }
200
+ return undefined;
201
+ }
202
+ });
203
+ if (p.isCancel(projectPathInput)) {
204
+ p.cancel('Operation cancelled');
205
+ process.exit(0);
206
+ }
207
+ projectPath = projectPathInput;
208
+ }
209
+ projectDirectory = path.resolve(process.cwd(), projectPath);
210
+ const projectName = path.basename(projectDirectory);
211
+ const packageManager = detectPackageManagerFromEnvironment();
212
+ try {
213
+ await fs.mkdir(projectDirectory, {
214
+ recursive: true
215
+ });
216
+ } catch (error) {
217
+ p.log.error(`Failed to create directory: ${error instanceof Error ? error.message : 'Unknown error'}`);
218
+ process.exit(1);
219
+ }
220
+ const resolvedSource = {
221
+ ...DEFAULT_TEMPLATE_SOURCE,
222
+ ...templateSource
223
+ };
224
+ s.start(`Downloading template "${resolvedSource.templatePath}" from ` + `${resolvedSource.owner}/${resolvedSource.repo}@${resolvedSource.ref}...`);
225
+ try {
226
+ await scaffoldProject(projectDirectory, projectName, packageManager, templateSource, options.payloadVersion);
227
+ s.stop(pc.green('✓ Template downloaded'));
228
+ s.start('Installing dependencies...');
229
+ await installDependencies(projectDirectory, packageManager);
230
+ s.stop(pc.green('✓ Dependencies installed'));
231
+ } catch (error) {
232
+ s.stop(pc.red('✗ Failed'));
233
+ log.error(error instanceof Error ? error.message : 'Unknown error');
234
+ process.exit(1);
235
+ }
236
+ }
237
+ let cmsResourceId = options.id;
238
+ if (!cmsResourceId) {
239
+ if (!credential) {
240
+ p.log.error('Authentication is required to create a project');
241
+ process.exit(1);
242
+ }
243
+ const createdProject = await promptToCreateProject({
244
+ credential,
245
+ projectPath: projectDirectory,
246
+ resourceType
247
+ });
248
+ cmsResourceId = createdProject.id;
249
+ await addOrUpdateEnvVar(projectDirectory, 'FIGMA_PROJECT_ID', cmsResourceId);
250
+ }
143
251
  // Fetch bootstrap info (oauth creds + environments)
144
252
  let bootstrapInfo = null;
145
253
  let resolvedEnv = null;
146
254
  let oauthClientId = null;
147
255
  let oauthClientSecret = null;
148
256
  if (!options.skipAuth) {
149
- const credential = await tryGetCredential(tokenStore);
150
257
  if (credential) {
151
258
  try {
152
259
  bootstrapInfo = await getBootstrapInfo(credential, cmsResourceId);
@@ -178,8 +285,23 @@ async function maybeInstallPayloadSkill(args) {
178
285
  }
179
286
  }
180
287
  }
181
- // Step 4: Detect or scaffold Payload project
182
- const isInProject = await isInProjectDirectory();
288
+ // Step 4: Detect or scaffold project
289
+ if (isInProject && resourceType === 'vite') {
290
+ try {
291
+ await writeProjectConfiguration({
292
+ bootstrapInfo,
293
+ environmentName: resolvedEnv?.name ?? 'production',
294
+ projectDir: process.cwd(),
295
+ projectId: cmsResourceId,
296
+ tokenStore
297
+ });
298
+ } catch (error) {
299
+ p.log.error(error instanceof Error ? error.message : 'Failed to configure project');
300
+ process.exit(1);
301
+ }
302
+ p.outro(pc.green('✓ Vite project initialized successfully!'));
303
+ return;
304
+ }
183
305
  if (isInProject) {
184
306
  // ===== EXISTING PROJECT FLOW =====
185
307
  log.debug('Project directory detected');
@@ -194,6 +316,9 @@ async function maybeInstallPayloadSkill(args) {
194
316
  process.exit(1);
195
317
  }
196
318
  p.log.success(pc.green(`✓ Payload ${projectInfo.payloadVersion || 'detected'}`));
319
+ // Approvals must exist before any install: pnpm v11 aborts on unapproved
320
+ // build scripts, and re-init runs against an existing install.
321
+ await ensurePnpmWorkspace(process.cwd());
197
322
  // Check dependencies
198
323
  s.start('Checking dependencies...');
199
324
  const hasDeps = await hasRequiredDependencies(process.cwd());
@@ -274,8 +399,13 @@ async function maybeInstallPayloadSkill(args) {
274
399
  '.env.local',
275
400
  'lambda.zip'
276
401
  ]);
277
- await maybeInstallPayloadSkill({
278
- noSkill: options.noSkill,
402
+ await runProjectCodegen({
403
+ packageManager,
404
+ projectDir: process.cwd(),
405
+ spinner: s
406
+ });
407
+ await maybeWriteAgentConfigFiles({
408
+ noAgent: options.noAgent,
279
409
  projectDir: process.cwd()
280
410
  });
281
411
  // Generate project token (unless skipping auth)
@@ -293,58 +423,8 @@ async function maybeInstallPayloadSkill(args) {
293
423
  p.note(nextSteps.join('\n'), 'Next Steps');
294
424
  } else {
295
425
  // ===== NEW PROJECT FLOW =====
296
- // p.log.info('No project detected - Creating New Project')
297
- let projectPath;
298
- if (options.name) {
299
- projectPath = options.name;
300
- } else {
301
- const projectPathInput = await p.text({
302
- initialValue: './',
303
- message: 'Enter path to create project:',
304
- placeholder: './my-cms-project',
305
- validate: (value)=>{
306
- if (!value) {
307
- return 'Path is required';
308
- }
309
- // Allow relative or absolute paths
310
- return undefined;
311
- }
312
- });
313
- if (p.isCancel(projectPathInput)) {
314
- p.cancel('Operation cancelled');
315
- process.exit(0);
316
- }
317
- projectPath = projectPathInput;
318
- }
319
- const fullPath = path.resolve(process.cwd(), projectPath);
320
- const projectName = path.basename(fullPath);
426
+ const fullPath = projectDirectory;
321
427
  const packageManager = detectPackageManagerFromEnvironment();
322
- // Create directory
323
- try {
324
- await fs.mkdir(fullPath, {
325
- recursive: true
326
- });
327
- } catch (error) {
328
- p.log.error(`Failed to create directory: ${error instanceof Error ? error.message : 'Unknown error'}`);
329
- process.exit(1);
330
- }
331
- // Scaffold project
332
- const resolvedSource = {
333
- ...DEFAULT_TEMPLATE_SOURCE,
334
- ...templateSource
335
- };
336
- s.start(`Downloading template "${resolvedSource.templatePath}" from ` + `${resolvedSource.owner}/${resolvedSource.repo}@${resolvedSource.ref}...`);
337
- try {
338
- await scaffoldProject(fullPath, projectName, packageManager, templateSource, options.payloadVersion);
339
- s.stop(pc.green('✓ Template downloaded'));
340
- s.start('Installing dependencies...');
341
- await installDependencies(fullPath, packageManager);
342
- s.stop(pc.green('✓ Dependencies installed'));
343
- } catch (error) {
344
- s.stop(pc.red('✗ Failed'));
345
- log.error(error instanceof Error ? error.message : 'Unknown error');
346
- process.exit(1);
347
- }
348
428
  // Modify Payload configuration
349
429
  s.start('Checking Payload configuration...');
350
430
  const modResult = await ensurePayloadFigmaConfig(fullPath, packageManager, {
@@ -398,8 +478,13 @@ async function maybeInstallPayloadSkill(args) {
398
478
  log.error(error instanceof Error ? error.message : 'Unknown error');
399
479
  process.exit(1);
400
480
  }
401
- await maybeInstallPayloadSkill({
402
- noSkill: options.noSkill,
481
+ await runProjectCodegen({
482
+ packageManager,
483
+ projectDir: fullPath,
484
+ spinner: s
485
+ });
486
+ await maybeWriteAgentConfigFiles({
487
+ noAgent: options.noAgent,
403
488
  projectDir: fullPath
404
489
  });
405
490
  // Generate project token (unless skipping auth)
@@ -1,8 +1,18 @@
1
1
  type LoginArgs = {
2
+ /** Redirect URL returned by the browser for resumable login completion. */
3
+ callbackUrl?: string;
4
+ /** Pending resumable login session to complete. */
5
+ completeSessionId?: string;
6
+ /** Start a resumable login without waiting on stdin or a callback server. */
7
+ headless?: boolean;
8
+ /** Emit machine-readable output for resumable login. */
9
+ json?: boolean;
10
+ /** Open the browser without asking for confirmation. */
11
+ openBrowserWithoutPrompt?: boolean;
2
12
  /** Set this to show next steps after login
3
13
  * @default true
4
14
  */
5
- showNextSteps: boolean;
15
+ showNextSteps?: boolean;
6
16
  };
7
17
  /**
8
18
  * Handle the `@payloadcms/figma login` command
@@ -1,6 +1,7 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import pc from 'picocolors';
3
3
  import { CallbackError } from '../auth/callback-server.js';
4
+ import { completeHeadlessOAuthFlow, startHeadlessOAuthFlow } from '../auth/headless-oauth-flow.js';
4
5
  import { executeOAuthFlow, getValidCredential, OAuthFlowError } from '../auth/oauth-flow.js';
5
6
  import { getTokenStore } from '../auth/token-store.js';
6
7
  import { getOAuthConfig } from '../config/oauth.js';
@@ -13,11 +14,47 @@ import * as log from '../utils/log.js';
13
14
  * Authenticates the user with Figma OAuth2 and stores tokens locally.
14
15
  * If valid tokens already exist, shows an error message.
15
16
  */ export async function loginCommand(args) {
16
- const { showNextSteps } = args || {
17
- showNextSteps: false
18
- };
17
+ const { callbackUrl, completeSessionId, headless = false, json = false, openBrowserWithoutPrompt = false, showNextSteps = false } = args ?? {};
19
18
  const env = getInfraEnvironment();
20
19
  const tokenStore = getTokenStore(env);
20
+ if (headless && completeSessionId) {
21
+ throw new Error('Use either --headless or --complete, not both.');
22
+ }
23
+ if (completeSessionId) {
24
+ if (!callbackUrl) {
25
+ throw new Error('--callback-url is required with --complete.');
26
+ }
27
+ const result = await completeHeadlessOAuthFlow({
28
+ callbackUrl,
29
+ sessionId: completeSessionId,
30
+ tokenStore
31
+ });
32
+ reportHeadlessLoginCompleted({
33
+ json,
34
+ userId: result.userId
35
+ });
36
+ return;
37
+ }
38
+ if (headless) {
39
+ try {
40
+ const credential = await getValidCredential(tokenStore);
41
+ if (credential) {
42
+ reportHeadlessLoginCompleted({
43
+ json,
44
+ userId: tokenStore.getOauthInfo()?.userId
45
+ });
46
+ return;
47
+ }
48
+ } catch {
49
+ // Invalid cached credentials should start a new login session.
50
+ }
51
+ const result = startHeadlessOAuthFlow(tokenStore);
52
+ reportHeadlessLoginStarted({
53
+ json,
54
+ ...result
55
+ });
56
+ return;
57
+ }
21
58
  // Check for existing valid tokens
22
59
  try {
23
60
  const existingCredential = await getValidCredential(tokenStore);
@@ -37,18 +74,43 @@ import * as log from '../utils/log.js';
37
74
  }
38
75
  // Start OAuth flow
39
76
  await handleAuthentication({
77
+ openBrowserWithoutPrompt,
40
78
  showNextSteps,
41
79
  tokenStore
42
80
  });
43
81
  }
82
+ function reportHeadlessLoginStarted({ authorizationUrl, expiresAt, json, sessionId }) {
83
+ if (json) {
84
+ process.stdout.write(`${JSON.stringify({
85
+ authorizationUrl,
86
+ expiresAt,
87
+ sessionId,
88
+ status: 'authorization_required'
89
+ })}\n`);
90
+ return;
91
+ }
92
+ p.log.step(`Open this URL to authenticate:\n\n ${authorizationUrl}`);
93
+ p.note(`After approving, copy the full redirected URL and run:\n` + `@payloadcms/figma login --complete ${sessionId} --callback-url '<redirect-url>'`, 'Complete login');
94
+ }
95
+ function reportHeadlessLoginCompleted({ json, userId }) {
96
+ if (json) {
97
+ process.stdout.write(`${JSON.stringify({
98
+ status: 'authenticated',
99
+ userId
100
+ })}\n`);
101
+ return;
102
+ }
103
+ p.log.success(pc.green('✓ Logged in'));
104
+ }
44
105
  /**
45
106
  * Handle authentication flow
46
- */ async function handleAuthentication({ showNextSteps, tokenStore }) {
107
+ */ async function handleAuthentication({ openBrowserWithoutPrompt, showNextSteps, tokenStore }) {
47
108
  try {
48
109
  // Execute OAuth flow
49
110
  const oauthConfig = getOAuthConfig();
50
111
  const result = await executeOAuthFlow(tokenStore, {
51
112
  clientId: oauthConfig.clientId,
113
+ openBrowserWithoutPrompt,
52
114
  redirectUri: oauthConfig.redirectUri,
53
115
  scopes: oauthConfig.scopes
54
116
  });
@@ -0,0 +1 @@
1
+ export declare function formatProjectSubdomain(value: string): string;
@@ -0,0 +1,3 @@
1
+ export function formatProjectSubdomain(value) {
2
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 60).replace(/^-|-$/g, '') || 'figma-project';
3
+ }
@@ -0,0 +1 @@
1
+ export declare function formatUnavailableSubdomainReason(reason: string): string;
@@ -0,0 +1,7 @@
1
+ export function formatUnavailableSubdomainReason(reason) {
2
+ if (reason === 'already_taken') {
3
+ return 'Subdomain is already taken. Choose another.';
4
+ }
5
+ const reasonDetail = reason ? `: ${reason.replaceAll('_', ' ')}` : '';
6
+ return `Subdomain is unavailable${reasonDetail}. Choose another.`;
7
+ }
@@ -0,0 +1 @@
1
+ export declare function getDefaultProjectName(projectPath: string): Promise<string>;