@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
@@ -0,0 +1,19 @@
1
+ // The Content API stores every document id as a string, but Payload keeps a
2
+ // collection's custom numeric id as a `number`. Stringify numbers so an id filter
3
+ // matches the stored value.
4
+ // Has two shapes it accepts:
5
+ // - Scalar values (most operators)
6
+ // - Arrays of values (for `in`/`not_in` operators)
7
+ // Non-numeric entries (already-string ids) pass through untouched.
8
+ export function stringifyNumericIds(value) {
9
+ if (typeof value === 'number') {
10
+ return stringifyNumericId(value);
11
+ }
12
+ if (Array.isArray(value)) {
13
+ return value.map((entry)=>stringifyNumericId(entry));
14
+ }
15
+ return value;
16
+ }
17
+ function stringifyNumericId(value) {
18
+ return typeof value === 'number' ? String(value) : value;
19
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A plain, string-keyed object — the shape that can safely be spread and have
3
+ * properties read off it without tripping over arrays, `null`, or exotic objects.
4
+ *
5
+ * Objects carrying symbol-keyed own properties are rejected: symbol keys are how
6
+ * libraries tag a value as something more than data, and a spread copies only the
7
+ * string keys, so treating such a value as a record silently strips the tag.
8
+ */
9
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A plain, string-keyed object — the shape that can safely be spread and have
3
+ * properties read off it without tripping over arrays, `null`, or exotic objects.
4
+ *
5
+ * Objects carrying symbol-keyed own properties are rejected: symbol keys are how
6
+ * libraries tag a value as something more than data, and a spread copies only the
7
+ * string keys, so treating such a value as a record silently strips the tag.
8
+ */ export function isRecord(value) {
9
+ if (value == null) {
10
+ return false;
11
+ }
12
+ if (typeof value !== 'object') {
13
+ return false;
14
+ }
15
+ if (Array.isArray(value)) {
16
+ return false;
17
+ }
18
+ return Object.getOwnPropertySymbols(value).length === 0;
19
+ }
@@ -1,3 +1,4 @@
1
+ import { stringifyNumericIds } from './data/stringifyNumericIds.js';
1
2
  export function convertPayloadWhereToContentAPI(where, options = {}) {
2
3
  // Empty where {} should be { and: [] } not undefined
3
4
  // Content API requires a where clause structure even for "no filter"
@@ -42,7 +43,7 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
42
43
  conditions.push({
43
44
  operator: 'equals',
44
45
  path: fieldPath,
45
- value: fieldPath === 'id' && typeof value === 'number' ? String(value) : value
46
+ value: fieldPath === 'id' ? stringifyNumericIds(value) : value
46
47
  });
47
48
  continue;
48
49
  }
@@ -69,9 +70,10 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
69
70
  }
70
71
  // Content API stores all document IDs as strings.
71
72
  // Payload sends numeric values for collections with custom numeric ID fields,
72
- // so we must stringify to match.
73
- if (fieldPath === 'id' && typeof finalValue === 'number') {
74
- finalValue = String(finalValue);
73
+ // so we must stringify to match. This includes the `in`/`not_in` arrays that
74
+ // Payload's relationship-population dataloader always uses ({ id: { in: [...] } }).
75
+ if (fieldPath === 'id') {
76
+ finalValue = stringifyNumericIds(finalValue);
75
77
  }
76
78
  conditions.push({
77
79
  operator: op,
@@ -0,0 +1,12 @@
1
+ export type WriteAgentConfigResult = {
2
+ agentsMd: 'appended' | 'created' | 'unchanged';
3
+ claudeMd: 'appended' | 'created' | 'unchanged';
4
+ };
5
+ /**
6
+ * Write the root AI-agent pointer files so assistants can find the Payload
7
+ * skill bundled in node_modules. Idempotent: creates when absent, appends the
8
+ * pointer when a file exists without it, and no-ops when already present.
9
+ */
10
+ export declare function writeAgentConfigFiles(args: {
11
+ projectDir: string;
12
+ }): Promise<WriteAgentConfigResult>;
@@ -0,0 +1,52 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ const SKILL_POINTER_PATH = 'node_modules/payload/skills/payload/SKILL.md';
4
+ const AGENTS_BLOCK = `# AI Agent
5
+
6
+ For any Payload-related work, reference the skill at
7
+ \`${SKILL_POINTER_PATH}\`.
8
+ `;
9
+ const CLAUDE_IMPORT = '@AGENTS.md';
10
+ /**
11
+ * Write the root AI-agent pointer files so assistants can find the Payload
12
+ * skill bundled in node_modules. Idempotent: creates when absent, appends the
13
+ * pointer when a file exists without it, and no-ops when already present.
14
+ */ export async function writeAgentConfigFiles(args) {
15
+ const agentsMd = await ensureFileContains({
16
+ content: AGENTS_BLOCK,
17
+ filePath: path.join(args.projectDir, 'AGENTS.md'),
18
+ marker: SKILL_POINTER_PATH
19
+ });
20
+ const claudeMd = await ensureFileContains({
21
+ content: `${CLAUDE_IMPORT}\n`,
22
+ filePath: path.join(args.projectDir, 'CLAUDE.md'),
23
+ marker: CLAUDE_IMPORT
24
+ });
25
+ return {
26
+ agentsMd,
27
+ claudeMd
28
+ };
29
+ }
30
+ async function ensureFileContains(args) {
31
+ const existing = await readFileOrNull(args.filePath);
32
+ if (existing === null) {
33
+ await fs.writeFile(args.filePath, args.content, 'utf8');
34
+ return 'created';
35
+ }
36
+ if (existing.includes(args.marker)) {
37
+ return 'unchanged';
38
+ }
39
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
40
+ await fs.writeFile(args.filePath, `${existing}${separator}${args.content}`, 'utf8');
41
+ return 'appended';
42
+ }
43
+ async function readFileOrNull(filePath) {
44
+ try {
45
+ return await fs.readFile(filePath, 'utf8');
46
+ } catch (err) {
47
+ if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
48
+ return null;
49
+ }
50
+ throw err;
51
+ }
52
+ }
@@ -0,0 +1,234 @@
1
+ ---
2
+ name: deploy-to-figma
3
+ description: This skill should be used when the user asks to "deploy to Figma", "publish to Figma", "ship to Figma", "deploy my app to Figma Cloud", "put this Payload site on Figma", or otherwise wants to deploy the current project to Figma Cloud. You (the agent) inspect, classify, and build the target project; the @payloadcms/figma CLI only authenticates and deploys the verified artifact you hand it.
4
+ version: 0.2.0
5
+ allowed-tools: Read, Bash, AskUserQuestion
6
+ ---
7
+
8
+ # Deploy to Figma Cloud
9
+
10
+ Deploy a project to Figma Cloud with the `@payloadcms/figma` CLI. **You** (the
11
+ agent) determine the deploy shape — you inspect the project, run the build, and
12
+ verify the real build artifacts, then hand the CLI an explicit adapter and
13
+ output root you have confirmed on disk. The CLI no longer inspects the project;
14
+ it only authenticates and ships the artifact you point it at.
15
+
16
+ **Golden rule:** detection is your job. Read files and inspect real artifacts —
17
+ do not ask `@payloadcms/figma` what framework this is, and do not guess. When the
18
+ evidence conflicts or the shape isn't one Figma Cloud supports, **stop** before
19
+ touching auth or deploy.
20
+
21
+ Run the CLI with `npx @payloadcms/figma@alpha <...>` **from the target folder**.
22
+ If a different invocation is already established in the repo (e.g.
23
+ `pnpm start --`), use that instead — but `npx @payloadcms/figma@alpha` is the
24
+ default.
25
+
26
+ If the user's request contains **“Figma staging”** (case-insensitive), append
27
+ `--infra-env staging` to every CLI invocation in this flow, including login,
28
+ login completion, deploy, and deploy retries. Otherwise omit the flag.
29
+
30
+ ## Flow overview
31
+
32
+ 1. **Resolve** the target project directory.
33
+ 2. **Inspect & classify** the deploy shape from evidence — or stop if unsupported/ambiguous.
34
+ 3. **Build & verify** the real artifact (buildable projects); static folders skip the build.
35
+ 4. **Detect initialization** from `target/.env`; gather creation inputs if uninitialized.
36
+ 5. **Preflight auth** with the resumable headless flow.
37
+ 6. **Deploy** the verified artifact with an explicit `--adapter` and `--output`.
38
+ 7. **Report** the deployed URL.
39
+
40
+ Exact CLI contracts (flags, JSON shapes, artifact signatures) live in
41
+ `references/cli-reference.md` — consult it when a flag or field is unclear.
42
+
43
+ ## Step 1 — Resolve the target directory
44
+
45
+ - If the prompt names a directory, that is the target.
46
+ - Otherwise use the current folder.
47
+ - In a **monorepo**, operate from the target **package** folder — not the repo
48
+ root. If several plausible apps exist and none is clearly the target, **ask**
49
+ via `AskUserQuestion` rather than guessing.
50
+
51
+ Every command and the build run **from the target folder**.
52
+
53
+ ## Step 2 — Inspect and classify the deploy shape
54
+
55
+ Gather evidence yourself with `Read`/`Bash`. Do **not** rely on
56
+ `@payloadcms/figma` for detection. Look at:
57
+
58
+ - `package.json` — `dependencies`/`devDependencies`, `scripts.build`, and
59
+ `packageManager`.
60
+ - Lockfiles — `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `bun.lockb`.
61
+ - Framework config — `next.config.*`, `vite.config.*`, `nitro.config.*`,
62
+ `nuxt.config.*`, `app.config.*`.
63
+ - Source layout and any build artifacts already on disk.
64
+
65
+ Map the evidence to one **supported deploy shape**:
66
+
67
+ | Evidence | Shape | `--adapter` |
68
+ | --- | --- | --- |
69
+ | `next` dependency / `next.config.*` / a `next build` script | Next.js | `nextjs` |
70
+ | Nitro, Nuxt, TanStack Start, or any other evidenced Nitro output | Nitro | `nitro` |
71
+ | A built (or buildable) Vite single-page app | Vite | `vite` |
72
+ | A simple static folder — `index.html` plus CSS/JS/assets, no build needed (even with **no** `package.json`) | Static | `vite`, output = that folder |
73
+
74
+ If the evidence fits **none** of these, or points at **more than one** framework
75
+ with no clear winner, or you otherwise can't land confidently on one supported
76
+ shape → **go to Step 8 and stop**. Do not build, authenticate, or deploy.
77
+
78
+ ## Step 3 — Build and verify the artifact
79
+
80
+ **Buildable projects** (Next.js / Nitro / Vite): run the build **from the target
81
+ folder, before** any auth or project creation, with `FIGMA_EXTRACT_SCHEDULES=true`.
82
+
83
+ Pick the package manager by searching the **target folder and its ancestors**
84
+ (up to the repo/monorepo root) — in a monorepo the `packageManager` field and
85
+ the lockfile usually live at the workspace root, not in the target package.
86
+ Precedence: nearest `packageManager` field → nearest lockfile
87
+ (`pnpm-lock.yaml`→pnpm, `package-lock.json`→npm, `yarn.lock`→yarn,
88
+ `bun.lockb`→bun) → available tooling. Run the build itself **from the target
89
+ package folder** with whichever manager you resolved, e.g.:
90
+
91
+ ```bash
92
+ FIGMA_EXTRACT_SCHEDULES=true npm run build
93
+ ```
94
+
95
+ Then **inspect the actual artifacts** to confirm the adapter and the output
96
+ **root**:
97
+
98
+ - **Next.js** — the root contains `BUILD_ID` and standalone server output. Pass
99
+ the root (e.g. `.next`) — **never** `.next/standalone`.
100
+ - **Nitro** — the root contains `server/index.mjs` (typically `.output`).
101
+ - **Vite** — the root contains `index.html` (typically `dist`).
102
+
103
+ **Static folders** skip the build entirely. The output is the exact folder that
104
+ contains `index.html` (often `.`).
105
+
106
+ If the build fails, or the artifacts don't match the shape you classified (the
107
+ evidence now conflicts), **stop** — go to Step 8. Never pass an `--output` root
108
+ you have not confirmed exists on disk.
109
+
110
+ ## Step 4 — Detect initialization; gather creation inputs
111
+
112
+ Detect initialization **only** by testing whether `target/.env` contains a
113
+ nonempty `FIGMA_PROJECT_ID`, e.g.:
114
+
115
+ ```bash
116
+ test -f .env && grep -Eq '^FIGMA_PROJECT_ID=.+' .env && echo initialized || echo uninitialized
117
+ ```
118
+
119
+ Do **not** print the value or dump `.env`.
120
+
121
+ **Initialized** (`FIGMA_PROJECT_ID` present) → skip this step; pass no creation flags.
122
+
123
+ **Uninitialized** → gather the five project-creation fields across **two** grouped
124
+ `AskUserQuestion` interactions (a single call accepts at most four questions),
125
+ each field offering the CLI default so the user can accept as-is:
126
+
127
+ **Interaction 1 — naming** (three questions):
128
+
129
+ | Field | CLI default | Deploy flag |
130
+ | --- | --- | --- |
131
+ | Project name | derived from the directory name | `--name` |
132
+ | Description | `"<name> project"` | `--description` |
133
+ | Subdomain | slugified from the project name | `--subdomain` |
134
+
135
+ **Interaction 2 — placement** (two questions):
136
+
137
+ | Field | CLI default | Deploy flag |
138
+ | --- | --- | --- |
139
+ | Workspace | the account's default Figma workspace | `--workspace` |
140
+ | Region | `us-east-1` | `--region` |
141
+
142
+ Present the defaults as the recommended option. For any field left at its
143
+ default, **omit** the corresponding flag and let the CLI apply the default — only
144
+ pass a flag when the user supplied a non-default value.
145
+
146
+ ## Step 5 — Preflight authentication
147
+
148
+ The deploy command needs a valid credential and cannot open a browser in a
149
+ headless/agent context, so authenticate first with the resumable headless flow.
150
+
151
+ ```bash
152
+ npx @payloadcms/figma@alpha login --headless --json
153
+ ```
154
+
155
+ Parse `status`:
156
+
157
+ - `authenticated` — already logged in. Proceed to deploy.
158
+ - `authorization_required` — the JSON includes `authorizationUrl` and
159
+ `sessionId`. Give the user the `authorizationUrl`, ask them to open it,
160
+ approve, and paste back **the full redirected URL** (the
161
+ `http://localhost:.../callback?...` address the browser lands on). Then:
162
+
163
+ ```bash
164
+ npx @payloadcms/figma@alpha login --complete <sessionId> --callback-url '<redirect-url>' --json
165
+ ```
166
+
167
+ Quote the callback URL in single quotes. Expect `{"status":"authenticated",...}`.
168
+
169
+ **Handle the callback URL as a one-time secret:** pass it directly to the
170
+ `--complete` command and nothing else. Do not echo it back, summarize it, store
171
+ it in a file, or repeat it in later messages.
172
+
173
+ If completion fails, report the CLI's error and let the user retry the login
174
+ step; do not loop silently.
175
+
176
+ ## Step 6 — Deploy
177
+
178
+ Run **from the target folder**, non-interactively with `-y`. Always pass
179
+ `--skip-build` (you already built, or a static folder needs no build) and the
180
+ explicit `--adapter` / `--output` you verified in Steps 2–3:
181
+
182
+ ```bash
183
+ npx @payloadcms/figma@alpha deploy -y --skip-build --adapter <nextjs|nitro|vite> --output <verified-root>
184
+ ```
185
+
186
+ Add the project-creation flags from Step 4 only for an uninitialized project.
187
+
188
+ Examples:
189
+
190
+ ```bash
191
+ # Next.js app in a monorepo package (already initialized), built to .next
192
+ npx @payloadcms/figma@alpha deploy -y --skip-build --adapter nextjs --output .next
193
+
194
+ # Vite single-page app, built to dist
195
+ npx @payloadcms/figma@alpha deploy -y --skip-build --adapter vite --output dist
196
+
197
+ # Simple static folder (index.html at the folder root), no build
198
+ npx @payloadcms/figma@alpha deploy -y --skip-build --adapter vite --output .
199
+ ```
200
+
201
+ ### Deterministic retry — workspace selection
202
+
203
+ If deploy fails with a message like:
204
+
205
+ > Multiple eligible Figma workspaces. Pass --workspace `<plan-key>`. Available: Acme (plan_abc), Beta (plan_xyz)
206
+
207
+ re-run the exact same command with `--workspace <plan-key>` added, choosing the
208
+ plan key the user selected (ask via `AskUserQuestion` if not already known,
209
+ listing the workspaces from the error's `Available:` list). This is the only
210
+ retry to perform automatically. For other errors, surface the CLI message and stop.
211
+
212
+ ## Step 7 — Report the result
213
+
214
+ On success the CLI prints a line like `URL: https://<subdomain>.<domain>`.
215
+ Extract that URL and report it to the user as the deployed address, noting the
216
+ CLI's own message that a deploy usually takes 2–3 minutes to finish.
217
+
218
+ ## Step 8 — Unsupported or ambiguous → stop
219
+
220
+ If Steps 2–3 can't land confidently on one supported shape, **stop before auth
221
+ or deploy**. Tell the user what you found and quote the supported set:
222
+
223
+ > Figma Cloud supports Next.js, Vite single-page apps, and any Nitro-based app
224
+ > (including Nuxt and TanStack Start).
225
+
226
+ Never run `login` or `deploy` for an unsupported or ambiguous project. Decide
227
+ this **before** asking an uninitialized project's creation questions — never make
228
+ the user answer prompts only to fail on an unsupported framework.
229
+
230
+ ## Additional resources
231
+
232
+ - **`references/cli-reference.md`** — the deploy/login flags this skill uses, the
233
+ `login ... --json` JSON shapes, and the on-disk artifact signatures that
234
+ confirm each adapter and output root.
@@ -0,0 +1,146 @@
1
+ # @payloadcms/figma CLI reference (deploy-to-figma)
2
+
3
+ Exact contracts the orchestration depends on. Source of truth is the CLI itself
4
+ (`payload/payload-plugin` in figma/figma); this file mirrors the flags and shapes
5
+ the skill reads. If something is unclear, run the command and inspect the raw
6
+ output rather than guessing.
7
+
8
+ The CLI does **not** inspect the project — there is no `deploy --inspect` and no
9
+ project-inspection JSON API. The agent classifies the deploy shape from evidence
10
+ (see `SKILL.md` Steps 2–3) and hands the CLI an explicit `--adapter` and
11
+ `--output`.
12
+
13
+ ## Classifying the deploy shape (agent-driven)
14
+
15
+ Detect the shape yourself from files on disk. Do **not** call `@payloadcms/figma`
16
+ to detect it.
17
+
18
+ | Evidence | adapter | typical output root |
19
+ | --- | --- | --- |
20
+ | Next.js — `next` dep, `next.config.*`, or a `next build` script | `nextjs` | `.next` |
21
+ | Nitro / Nuxt / TanStack Start / other evidenced Nitro output | `nitro` | `.output` |
22
+ | Built Vite single-page app | `vite` | `dist` |
23
+ | Simple static folder — `index.html` + CSS/JS/assets, no build (no `package.json` OK) | `vite` | the folder itself (often `.`) |
24
+
25
+ Framework config hints: Next.js `distDir` (`next.config.*`), Vite `build.outDir`
26
+ (`vite.config.*`), Nitro/Nuxt `output.dir` (`nitro.config.*` / `nuxt.config.*`).
27
+ Custom output dirs mean you must confirm the real root against artifacts below.
28
+
29
+ ### On-disk artifact signatures (confirm before deploy)
30
+
31
+ After building (or for a static folder, as-is), confirm the output **root** by
32
+ what it contains:
33
+
34
+ - **Next.js** — root contains `BUILD_ID` **and** standalone server output. Pass
35
+ the root such as `.next`; **never** `.next/standalone`.
36
+ - **Nitro** — root contains `server/index.mjs` (typically `.output`).
37
+ - **Vite / static** — root contains `index.html` (typically `dist`; a static
38
+ folder may be `.`).
39
+
40
+ If the build fails or the artifacts don't match the classified shape, **stop** —
41
+ do not pass an unverified `--output`.
42
+
43
+ ## Building
44
+
45
+ Run the target package's build **from the target folder**, before auth, with
46
+ `FIGMA_EXTRACT_SCHEDULES=true`. Choose the package manager by searching the
47
+ target folder **and its ancestors** (up to the repo/monorepo root) — the
48
+ `packageManager` field and the lockfile often live at the workspace root, not
49
+ the target package. Precedence: nearest `packageManager` field → nearest
50
+ lockfile (`pnpm-lock.yaml`→pnpm, `package-lock.json`→npm, `yarn.lock`→yarn,
51
+ `bun.lockb`→bun) → available tooling. Even so, run the build itself from the
52
+ target package folder. Static folders skip the build.
53
+
54
+ ```bash
55
+ FIGMA_EXTRACT_SCHEDULES=true npm run build
56
+ ```
57
+
58
+ ## `login --headless --json`
59
+
60
+ Starts (or short-circuits) a resumable login without a live terminal.
61
+
62
+ Already authenticated:
63
+
64
+ ```json
65
+ { "status": "authenticated", "userId": "..." }
66
+ ```
67
+
68
+ Authorization required:
69
+
70
+ ```json
71
+ {
72
+ "status": "authorization_required",
73
+ "authorizationUrl": "https://www.figma.com/oauth?...",
74
+ "sessionId": "sess_...",
75
+ "expiresAt": 1730000000000
76
+ }
77
+ ```
78
+
79
+ ## `login --complete <sessionId> --callback-url '<url>' --json`
80
+
81
+ Completes a pending headless login using the full redirect URL the browser
82
+ landed on (`http://localhost:<port>/callback?code=...&state=...`). Returns:
83
+
84
+ ```json
85
+ { "status": "authenticated", "userId": "..." }
86
+ ```
87
+
88
+ Treat the callback URL as a one-time secret: pass it once, never echo or persist it.
89
+
90
+ ## Infrastructure environment
91
+
92
+ When the user's request contains “Figma staging” (case-insensitive), append
93
+ `--infra-env staging` to every CLI command, including login, login completion,
94
+ deploy, and deploy retries. Otherwise omit the flag.
95
+
96
+ ## `deploy` flags used by this skill
97
+
98
+ The deploy command is always explicit — the agent supplies the verified adapter
99
+ and output root and skips the build (it already ran, or the folder is static):
100
+
101
+ ```bash
102
+ npx @payloadcms/figma@alpha deploy -y --skip-build --adapter <nextjs|nitro|vite> --output <root>
103
+ ```
104
+
105
+ | Flag | Purpose |
106
+ | --- | --- |
107
+ | `-y`, `--yes` | Non-interactive; safe project-creation defaults |
108
+ | `--infra-env staging` | Target Figma staging when explicitly requested |
109
+ | `--skip-build` | Always passed — deploy the artifact as-is, never rebuild |
110
+ | `--adapter <name>` | `nextjs` \| `vite` \| `nitro` — the shape you classified |
111
+ | `--output <path>` | The exact, verified build output root |
112
+ | `--name <name>` | Project name (uninitialized project creation) |
113
+ | `--description <text>` | Project description (uninitialized) |
114
+ | `--subdomain <sub>` | Requested subdomain (uninitialized) |
115
+ | `--workspace <planKey>` | Workspace plan key (uninitialized; also the retry flag) |
116
+ | `--region <region>` | Region, e.g. `us-east-1` (uninitialized) |
117
+
118
+ Initialization is detected only by a nonempty `FIGMA_PROJECT_ID` in `target/.env`
119
+ (never printed). Initialized projects pass no creation flags.
120
+
121
+ ## Deterministic deploy retry
122
+
123
+ When multiple eligible workspaces exist and none is a default, non-interactive
124
+ deploy fails with:
125
+
126
+ ```text
127
+ Multiple eligible Figma workspaces. Pass --workspace <plan-key>. Available: <Name> (<plan-key>), ...
128
+ ```
129
+
130
+ Re-run the identical command with `--workspace <plan-key>` for the chosen
131
+ workspace. An invalid `--workspace` value fails with:
132
+
133
+ ```text
134
+ Workspace plan key "<key>" is not eligible. Available: <Name> (<plan-key>), ...
135
+ ```
136
+
137
+ ## Success output
138
+
139
+ On success the CLI prints:
140
+
141
+ ```text
142
+ URL: https://<subdomain>.<domain>
143
+ This usually takes 2-3 minutes to complete.
144
+ ```
145
+
146
+ Extract the `URL:` line as the deployed address.
package/dist/types.d.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  import type arg from 'arg';
2
2
  export interface Args extends arg.Spec {
3
3
  '--adapter': StringConstructor;
4
+ '--callback-url': StringConstructor;
5
+ '--complete': StringConstructor;
4
6
  '--debug': BooleanConstructor;
7
+ '--description': StringConstructor;
5
8
  '--dry-run': BooleanConstructor;
6
9
  '--env': StringConstructor;
7
10
  '--environment': string;
8
11
  '--force': BooleanConstructor;
12
+ '--global': BooleanConstructor;
13
+ '--headless': BooleanConstructor;
9
14
  '--help': BooleanConstructor;
10
15
  '--id': StringConstructor;
11
16
  '--infra-env': StringConstructor;
@@ -13,7 +18,11 @@ export interface Args extends arg.Spec {
13
18
  '--logout': BooleanConstructor;
14
19
  '--name': StringConstructor;
15
20
  '--output': StringConstructor;
21
+ '--project': BooleanConstructor;
22
+ '--region': StringConstructor;
23
+ '--subdomain': StringConstructor;
16
24
  '--template': StringConstructor;
25
+ '--workspace': StringConstructor;
17
26
  '--yes': BooleanConstructor;
18
27
  '-e': string;
19
28
  '-f': string;
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import { IndentationText, Node, Project, SyntaxKind } from 'ts-morph';
4
+ import { ensurePnpmWorkspace } from './pnpm-builds.js';
4
5
  const RUN_SH_CONTENT = `#!/bin/bash -x
5
6
 
6
7
  [ ! -d '/tmp/cache' ] && mkdir -p /tmp/cache
@@ -19,21 +20,10 @@ NODE_ENV=production exec node server.js
19
20
  await fs.chmod(runShPath, 0o755);
20
21
  // 2. Modify next config to add standalone output
21
22
  await addNextConfigProperties(projectPath);
22
- // 3. Create/update .npmrc
23
- const npmrcPath = path.join(projectPath, '.npmrc');
24
- let npmrcContent = '';
25
- try {
26
- npmrcContent = await fs.readFile(npmrcPath, 'utf-8');
27
- } catch {
28
- // File doesn't exist, start fresh
29
- }
30
- if (!npmrcContent.includes('node-linker=hoisted')) {
31
- npmrcContent += npmrcContent && !npmrcContent.endsWith('\n') ? '\n' : '';
32
- npmrcContent += 'node-linker=hoisted\n';
33
- await fs.writeFile(npmrcPath, npmrcContent, 'utf-8');
34
- }
35
- // 4. Add lambda:buildzip script to package.json
23
+ // 3. Add lambda:buildzip script to package.json
36
24
  await addLambdaBuildScript(projectPath, packageManager);
25
+ // 4. Write pnpm-workspace.yaml (hoisted linker + build approvals)
26
+ await ensurePnpmWorkspace(projectPath);
37
27
  }
38
28
  /**
39
29
  * Find the Next.js config file, checking TS extensions first
@@ -14,6 +14,7 @@ export function helpMessage() {
14
14
  ${pc.cyan('logout')} Clear all stored tokens
15
15
  ${pc.cyan('list-tokens')} Show stored token information
16
16
  ${pc.cyan('init')} Initialize a Figma CMS project
17
+ ${pc.cyan('setup-agent')} Install or update the Figma deploy skill
17
18
  ${pc.cyan('bootstrap')} Print bootstrap info (tenant IDs, OAuth creds) for a project
18
19
  ${pc.cyan('debug')} Show debug info for troubleshooting
19
20
  ${pc.cyan('env')} Switch active environment
@@ -31,16 +32,20 @@ export function helpMessage() {
31
32
  ${pc.bold('AUTHENTICATION')}
32
33
 
33
34
  ${pc.cyan('@payloadcms/figma login')} Authenticate with Figma
35
+ ${pc.cyan('@payloadcms/figma login --headless --json')} Start resumable login for agents
36
+ ${pc.cyan('@payloadcms/figma login --complete <id> --callback-url <url>')} Complete resumable login
37
+ ${pc.dim('--json')} Emit machine-readable login status
34
38
  ${pc.cyan('@payloadcms/figma logout')} Clear tokens for current environment
35
39
  ${pc.cyan('@payloadcms/figma list-tokens')} Display stored OAuth and project tokens
36
40
 
37
41
  ${pc.bold('INIT COMMAND')}
38
42
 
39
- ${pc.cyan('@payloadcms/figma init --id <cms-resource-id>')} Initialize project
43
+ ${pc.cyan('@payloadcms/figma init')} Create and initialize project
44
+ ${pc.cyan('@payloadcms/figma init --id <cms-resource-id>')} Connect existing project
40
45
  ${pc.cyan('@payloadcms/figma init --id <id> --env staging')} Initialize for specific environment
41
46
  ${pc.dim('--name, -n <name>')} Set project directory name (skips prompt)
42
47
  ${pc.dim('--force')} Force reconfiguration of existing project
43
- ${pc.dim('--no-skill')} Skip installing the Payload skill into .claude/skills/payload/
48
+ ${pc.dim('--no-agent')} Skip writing AI agent config files (AGENTS.md, CLAUDE.md)
44
49
  ${pc.dim('--template, -t <spec>')} Override scaffold template (e.g. main:website, owner/repo#ref:path)
45
50
  ${pc.dim('--payload-version <tag|version>')} Core Payload npm dist-tag or version (default canary; e.g. latest, 4.0.0-canary.16)
46
51
 
@@ -58,14 +63,28 @@ export function helpMessage() {
58
63
  ${pc.bold('DEPLOY COMMAND')}
59
64
 
60
65
  ${pc.cyan('@payloadcms/figma deploy')} Deploy your project to Figma
66
+ ${pc.dim(' Creates a project when not initialized')}
61
67
  ${pc.dim('--id <project-id>')} Override FIGMA_PROJECT_ID
62
68
  ${pc.dim('--env <environment>')} Override FIGMA_ENVIRONMENT_NAME
63
- ${pc.dim('--yes, -y')} Skip confirmation prompts
69
+ ${pc.dim('--yes, -y')} Skip prompts; use safe defaults for project creation
70
+ ${pc.dim('--name <name>')} New project name (default: package/directory name)
71
+ ${pc.dim('--description <description>')} New project description (default: <name> project)
72
+ ${pc.dim('--subdomain <subdomain>')} New project subdomain (default: generated from name)
73
+ ${pc.dim('--workspace <plan-key>')} Workspace plan key (default workspace if omitted)
74
+ ${pc.dim('--region <region>')} New project region (default: us-east-1 non-interactively)
64
75
  ${pc.dim('--skip-build')} Skip building and use existing build
65
76
  ${pc.dim('--skip-size-check')} Skip the client-side bundle size check
66
77
  ${pc.dim('--adapter <nextjs|nitro|vite>')} Override framework detection
67
78
  ${pc.dim('--output <path>')} Exact framework build output root
68
79
 
80
+ ${pc.bold('SETUP AGENT COMMAND')}
81
+
82
+ ${pc.cyan('@payloadcms/figma setup-agent')} Prompt for global or project installation
83
+ ${pc.cyan('@payloadcms/figma setup-agent --global')} Install/update globally
84
+ ${pc.cyan('@payloadcms/figma setup-agent --project')} Install/update in the current project
85
+ ${pc.dim('--yes, -y')} Skip skills installer prompts
86
+ ${pc.dim('Non-interactive use requires --global or --project.')}
87
+
69
88
  ${pc.bold('UPGRADE COMMAND')}
70
89
 
71
90
  ${pc.cyan('@payloadcms/figma upgrade')} Run all upgrade migrations
@@ -0,0 +1,10 @@
1
+ export type PayloadGenerateResult = {
2
+ error: string;
3
+ ok: false;
4
+ } | {
5
+ ok: true;
6
+ };
7
+ /** Never throws. */
8
+ export declare function generateImportMap(projectPath: string): Promise<PayloadGenerateResult>;
9
+ /** Never throws. */
10
+ export declare function generateTypes(projectPath: string): Promise<PayloadGenerateResult>;