@payloadcms/figma 0.0.1-alpha.0

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 (305) hide show
  1. package/LICENSE.md +11 -0
  2. package/bin/cli.js +4 -0
  3. package/dist/api/control-plane.d.ts +124 -0
  4. package/dist/api/control-plane.d.ts.map +1 -0
  5. package/dist/api/control-plane.js +245 -0
  6. package/dist/api/control-plane.js.map +1 -0
  7. package/dist/api/control-plane.spec.d.ts +2 -0
  8. package/dist/api/control-plane.spec.d.ts.map +1 -0
  9. package/dist/api/control-plane.spec.js +296 -0
  10. package/dist/api/control-plane.spec.js.map +1 -0
  11. package/dist/api/figma-api.d.ts +31 -0
  12. package/dist/api/figma-api.d.ts.map +1 -0
  13. package/dist/api/figma-api.js +169 -0
  14. package/dist/api/figma-api.js.map +1 -0
  15. package/dist/api/figma-api.spec.d.ts +14 -0
  16. package/dist/api/figma-api.spec.d.ts.map +1 -0
  17. package/dist/api/figma-api.spec.js +201 -0
  18. package/dist/api/figma-api.spec.js.map +1 -0
  19. package/dist/auth/__tests__/fixtures.d.ts +26 -0
  20. package/dist/auth/__tests__/fixtures.d.ts.map +1 -0
  21. package/dist/auth/__tests__/fixtures.js +67 -0
  22. package/dist/auth/__tests__/fixtures.js.map +1 -0
  23. package/dist/auth/__tests__/mocks.d.ts +33 -0
  24. package/dist/auth/__tests__/mocks.d.ts.map +1 -0
  25. package/dist/auth/__tests__/mocks.js +63 -0
  26. package/dist/auth/__tests__/mocks.js.map +1 -0
  27. package/dist/auth/browser.d.ts +31 -0
  28. package/dist/auth/browser.d.ts.map +1 -0
  29. package/dist/auth/browser.js +41 -0
  30. package/dist/auth/browser.js.map +1 -0
  31. package/dist/auth/callback-server.d.ts +61 -0
  32. package/dist/auth/callback-server.d.ts.map +1 -0
  33. package/dist/auth/callback-server.js +168 -0
  34. package/dist/auth/callback-server.js.map +1 -0
  35. package/dist/auth/callback-server.spec.d.ts +2 -0
  36. package/dist/auth/callback-server.spec.d.ts.map +1 -0
  37. package/dist/auth/callback-server.spec.js +85 -0
  38. package/dist/auth/callback-server.spec.js.map +1 -0
  39. package/dist/auth/crypto-utils.d.ts +33 -0
  40. package/dist/auth/crypto-utils.d.ts.map +1 -0
  41. package/dist/auth/crypto-utils.js +96 -0
  42. package/dist/auth/crypto-utils.js.map +1 -0
  43. package/dist/auth/crypto-utils.spec.d.ts +2 -0
  44. package/dist/auth/crypto-utils.spec.d.ts.map +1 -0
  45. package/dist/auth/crypto-utils.spec.js +36 -0
  46. package/dist/auth/crypto-utils.spec.js.map +1 -0
  47. package/dist/auth/jwt-validator.d.ts +41 -0
  48. package/dist/auth/jwt-validator.d.ts.map +1 -0
  49. package/dist/auth/jwt-validator.js +113 -0
  50. package/dist/auth/jwt-validator.js.map +1 -0
  51. package/dist/auth/jwt-validator.spec.d.ts +10 -0
  52. package/dist/auth/jwt-validator.spec.d.ts.map +1 -0
  53. package/dist/auth/jwt-validator.spec.js +236 -0
  54. package/dist/auth/jwt-validator.spec.js.map +1 -0
  55. package/dist/auth/oauth-flow.d.ts +62 -0
  56. package/dist/auth/oauth-flow.d.ts.map +1 -0
  57. package/dist/auth/oauth-flow.js +172 -0
  58. package/dist/auth/oauth-flow.js.map +1 -0
  59. package/dist/auth/oauth-flow.spec.d.ts +2 -0
  60. package/dist/auth/oauth-flow.spec.d.ts.map +1 -0
  61. package/dist/auth/oauth-flow.spec.js +134 -0
  62. package/dist/auth/oauth-flow.spec.js.map +1 -0
  63. package/dist/auth/pages/error.html +85 -0
  64. package/dist/auth/pages/success.html +74 -0
  65. package/dist/auth/pkce.d.ts +51 -0
  66. package/dist/auth/pkce.d.ts.map +1 -0
  67. package/dist/auth/pkce.js +71 -0
  68. package/dist/auth/pkce.js.map +1 -0
  69. package/dist/auth/pkce.spec.d.ts +2 -0
  70. package/dist/auth/pkce.spec.d.ts.map +1 -0
  71. package/dist/auth/pkce.spec.js +32 -0
  72. package/dist/auth/pkce.spec.js.map +1 -0
  73. package/dist/auth/project-token.d.ts +47 -0
  74. package/dist/auth/project-token.d.ts.map +1 -0
  75. package/dist/auth/project-token.js +134 -0
  76. package/dist/auth/project-token.js.map +1 -0
  77. package/dist/auth/project-token.spec.d.ts +2 -0
  78. package/dist/auth/project-token.spec.d.ts.map +1 -0
  79. package/dist/auth/project-token.spec.js +332 -0
  80. package/dist/auth/project-token.spec.js.map +1 -0
  81. package/dist/auth/refresh.d.ts +35 -0
  82. package/dist/auth/refresh.d.ts.map +1 -0
  83. package/dist/auth/refresh.js +149 -0
  84. package/dist/auth/refresh.js.map +1 -0
  85. package/dist/auth/refresh.spec.d.ts +2 -0
  86. package/dist/auth/refresh.spec.d.ts.map +1 -0
  87. package/dist/auth/refresh.spec.js +105 -0
  88. package/dist/auth/refresh.spec.js.map +1 -0
  89. package/dist/auth/token-store.d.ts +110 -0
  90. package/dist/auth/token-store.d.ts.map +1 -0
  91. package/dist/auth/token-store.js +188 -0
  92. package/dist/auth/token-store.js.map +1 -0
  93. package/dist/auth/token-store.spec.d.ts +2 -0
  94. package/dist/auth/token-store.spec.d.ts.map +1 -0
  95. package/dist/auth/token-store.spec.js +276 -0
  96. package/dist/auth/token-store.spec.js.map +1 -0
  97. package/dist/auth/types.d.ts +197 -0
  98. package/dist/auth/types.d.ts.map +1 -0
  99. package/dist/auth/types.js +6 -0
  100. package/dist/auth/types.js.map +1 -0
  101. package/dist/cli.d.ts +5 -0
  102. package/dist/cli.d.ts.map +1 -0
  103. package/dist/cli.js +108 -0
  104. package/dist/cli.js.map +1 -0
  105. package/dist/commands/__tests__/test-utils.d.ts +28 -0
  106. package/dist/commands/__tests__/test-utils.d.ts.map +1 -0
  107. package/dist/commands/__tests__/test-utils.js +34 -0
  108. package/dist/commands/__tests__/test-utils.js.map +1 -0
  109. package/dist/commands/deploy.d.ts +16 -0
  110. package/dist/commands/deploy.d.ts.map +1 -0
  111. package/dist/commands/deploy.js +297 -0
  112. package/dist/commands/deploy.js.map +1 -0
  113. package/dist/commands/deploy.spec.d.ts +2 -0
  114. package/dist/commands/deploy.spec.d.ts.map +1 -0
  115. package/dist/commands/deploy.spec.js +18 -0
  116. package/dist/commands/deploy.spec.js.map +1 -0
  117. package/dist/commands/init.d.ts +30 -0
  118. package/dist/commands/init.d.ts.map +1 -0
  119. package/dist/commands/init.js +410 -0
  120. package/dist/commands/init.js.map +1 -0
  121. package/dist/commands/init.spec.d.ts +2 -0
  122. package/dist/commands/init.spec.d.ts.map +1 -0
  123. package/dist/commands/init.spec.js +283 -0
  124. package/dist/commands/init.spec.js.map +1 -0
  125. package/dist/commands/list-tokens.d.ts +16 -0
  126. package/dist/commands/list-tokens.d.ts.map +1 -0
  127. package/dist/commands/list-tokens.js +44 -0
  128. package/dist/commands/list-tokens.js.map +1 -0
  129. package/dist/commands/login.d.ts +17 -0
  130. package/dist/commands/login.d.ts.map +1 -0
  131. package/dist/commands/login.js +69 -0
  132. package/dist/commands/login.js.map +1 -0
  133. package/dist/commands/logout.d.ts +16 -0
  134. package/dist/commands/logout.d.ts.map +1 -0
  135. package/dist/commands/logout.js +43 -0
  136. package/dist/commands/logout.js.map +1 -0
  137. package/dist/config/oauth.d.ts +19 -0
  138. package/dist/config/oauth.d.ts.map +1 -0
  139. package/dist/config/oauth.js +43 -0
  140. package/dist/config/oauth.js.map +1 -0
  141. package/dist/constants.d.ts +27 -0
  142. package/dist/constants.d.ts.map +1 -0
  143. package/dist/constants.js +27 -0
  144. package/dist/constants.js.map +1 -0
  145. package/dist/index.d.ts +5 -0
  146. package/dist/index.d.ts.map +1 -0
  147. package/dist/index.js +6 -0
  148. package/dist/index.js.map +1 -0
  149. package/dist/plugin/adapter.d.ts +5 -0
  150. package/dist/plugin/adapter.d.ts.map +1 -0
  151. package/dist/plugin/adapter.js +139 -0
  152. package/dist/plugin/adapter.js.map +1 -0
  153. package/dist/plugin/build-config.d.ts +37 -0
  154. package/dist/plugin/build-config.d.ts.map +1 -0
  155. package/dist/plugin/build-config.js +25 -0
  156. package/dist/plugin/build-config.js.map +1 -0
  157. package/dist/plugin/build-config.spec.d.ts +2 -0
  158. package/dist/plugin/build-config.spec.d.ts.map +1 -0
  159. package/dist/plugin/build-config.spec.js +72 -0
  160. package/dist/plugin/build-config.spec.js.map +1 -0
  161. package/dist/types/config.d.ts +45 -0
  162. package/dist/types/config.d.ts.map +1 -0
  163. package/dist/types/config.js +9 -0
  164. package/dist/types/config.js.map +1 -0
  165. package/dist/types.d.ts +60 -0
  166. package/dist/types.d.ts.map +1 -0
  167. package/dist/types.js +3 -0
  168. package/dist/types.js.map +1 -0
  169. package/dist/utils/asset-collection.d.ts +30 -0
  170. package/dist/utils/asset-collection.d.ts.map +1 -0
  171. package/dist/utils/asset-collection.js +117 -0
  172. package/dist/utils/asset-collection.js.map +1 -0
  173. package/dist/utils/asset-collection.spec.d.ts +2 -0
  174. package/dist/utils/asset-collection.spec.d.ts.map +1 -0
  175. package/dist/utils/asset-collection.spec.js +155 -0
  176. package/dist/utils/asset-collection.spec.js.map +1 -0
  177. package/dist/utils/build-detection.d.ts +32 -0
  178. package/dist/utils/build-detection.d.ts.map +1 -0
  179. package/dist/utils/build-detection.js +55 -0
  180. package/dist/utils/build-detection.js.map +1 -0
  181. package/dist/utils/build-detection.spec.d.ts +2 -0
  182. package/dist/utils/build-detection.spec.d.ts.map +1 -0
  183. package/dist/utils/build-detection.spec.js +110 -0
  184. package/dist/utils/build-detection.spec.js.map +1 -0
  185. package/dist/utils/config.d.ts +33 -0
  186. package/dist/utils/config.d.ts.map +1 -0
  187. package/dist/utils/config.js +78 -0
  188. package/dist/utils/config.js.map +1 -0
  189. package/dist/utils/config.spec.d.ts +2 -0
  190. package/dist/utils/config.spec.d.ts.map +1 -0
  191. package/dist/utils/config.spec.js +167 -0
  192. package/dist/utils/config.spec.js.map +1 -0
  193. package/dist/utils/download-template.d.ts +15 -0
  194. package/dist/utils/download-template.d.ts.map +1 -0
  195. package/dist/utils/download-template.js +74 -0
  196. package/dist/utils/download-template.js.map +1 -0
  197. package/dist/utils/download-template.spec.d.ts +2 -0
  198. package/dist/utils/download-template.spec.d.ts.map +1 -0
  199. package/dist/utils/download-template.spec.js +76 -0
  200. package/dist/utils/download-template.spec.js.map +1 -0
  201. package/dist/utils/env-management.d.ts +16 -0
  202. package/dist/utils/env-management.d.ts.map +1 -0
  203. package/dist/utils/env-management.js +98 -0
  204. package/dist/utils/env-management.js.map +1 -0
  205. package/dist/utils/env-management.spec.d.ts +2 -0
  206. package/dist/utils/env-management.spec.d.ts.map +1 -0
  207. package/dist/utils/env-management.spec.js +123 -0
  208. package/dist/utils/env-management.spec.js.map +1 -0
  209. package/dist/utils/format.d.ts +23 -0
  210. package/dist/utils/format.d.ts.map +1 -0
  211. package/dist/utils/format.js +49 -0
  212. package/dist/utils/format.js.map +1 -0
  213. package/dist/utils/formatter.d.ts +11 -0
  214. package/dist/utils/formatter.d.ts.map +1 -0
  215. package/dist/utils/formatter.js +47 -0
  216. package/dist/utils/formatter.js.map +1 -0
  217. package/dist/utils/git.d.ts +15 -0
  218. package/dist/utils/git.d.ts.map +1 -0
  219. package/dist/utils/git.js +92 -0
  220. package/dist/utils/git.js.map +1 -0
  221. package/dist/utils/git.spec.d.ts +2 -0
  222. package/dist/utils/git.spec.d.ts.map +1 -0
  223. package/dist/utils/git.spec.js +99 -0
  224. package/dist/utils/git.spec.js.map +1 -0
  225. package/dist/utils/is-debug.d.ts +6 -0
  226. package/dist/utils/is-debug.d.ts.map +1 -0
  227. package/dist/utils/is-debug.js +8 -0
  228. package/dist/utils/is-debug.js.map +1 -0
  229. package/dist/utils/lambda-config.d.ts +7 -0
  230. package/dist/utils/lambda-config.d.ts.map +1 -0
  231. package/dist/utils/lambda-config.js +144 -0
  232. package/dist/utils/lambda-config.js.map +1 -0
  233. package/dist/utils/lambda-config.spec.d.ts +2 -0
  234. package/dist/utils/lambda-config.spec.d.ts.map +1 -0
  235. package/dist/utils/lambda-config.spec.js +141 -0
  236. package/dist/utils/lambda-config.spec.js.map +1 -0
  237. package/dist/utils/log.d.ts +6 -0
  238. package/dist/utils/log.d.ts.map +1 -0
  239. package/dist/utils/log.js +22 -0
  240. package/dist/utils/log.js.map +1 -0
  241. package/dist/utils/messages.d.ts +10 -0
  242. package/dist/utils/messages.d.ts.map +1 -0
  243. package/dist/utils/messages.js +96 -0
  244. package/dist/utils/messages.js.map +1 -0
  245. package/dist/utils/package-manager.d.ts +12 -0
  246. package/dist/utils/package-manager.d.ts.map +1 -0
  247. package/dist/utils/package-manager.js +37 -0
  248. package/dist/utils/package-manager.js.map +1 -0
  249. package/dist/utils/payload-config-ast.d.ts +30 -0
  250. package/dist/utils/payload-config-ast.d.ts.map +1 -0
  251. package/dist/utils/payload-config-ast.js +259 -0
  252. package/dist/utils/payload-config-ast.js.map +1 -0
  253. package/dist/utils/payload-config-ast.spec.d.ts +2 -0
  254. package/dist/utils/payload-config-ast.spec.d.ts.map +1 -0
  255. package/dist/utils/payload-config-ast.spec.js +303 -0
  256. package/dist/utils/payload-config-ast.spec.js.map +1 -0
  257. package/dist/utils/payload-config-finder.d.ts +9 -0
  258. package/dist/utils/payload-config-finder.d.ts.map +1 -0
  259. package/dist/utils/payload-config-finder.js +26 -0
  260. package/dist/utils/payload-config-finder.js.map +1 -0
  261. package/dist/utils/payload-config-modifier.d.ts +18 -0
  262. package/dist/utils/payload-config-modifier.d.ts.map +1 -0
  263. package/dist/utils/payload-config-modifier.int.spec.d.ts +2 -0
  264. package/dist/utils/payload-config-modifier.int.spec.d.ts.map +1 -0
  265. package/dist/utils/payload-config-modifier.int.spec.js +116 -0
  266. package/dist/utils/payload-config-modifier.int.spec.js.map +1 -0
  267. package/dist/utils/payload-config-modifier.js +232 -0
  268. package/dist/utils/payload-config-modifier.js.map +1 -0
  269. package/dist/utils/payload-config-modifier.spec.d.ts +2 -0
  270. package/dist/utils/payload-config-modifier.spec.d.ts.map +1 -0
  271. package/dist/utils/payload-config-modifier.spec.js +162 -0
  272. package/dist/utils/payload-config-modifier.spec.js.map +1 -0
  273. package/dist/utils/payload-package-check.d.ts +15 -0
  274. package/dist/utils/payload-package-check.d.ts.map +1 -0
  275. package/dist/utils/payload-package-check.js +116 -0
  276. package/dist/utils/payload-package-check.js.map +1 -0
  277. package/dist/utils/payload-package-check.spec.d.ts +2 -0
  278. package/dist/utils/payload-package-check.spec.d.ts.map +1 -0
  279. package/dist/utils/payload-package-check.spec.js +148 -0
  280. package/dist/utils/payload-package-check.spec.js.map +1 -0
  281. package/dist/utils/project.d.ts +58 -0
  282. package/dist/utils/project.d.ts.map +1 -0
  283. package/dist/utils/project.js +252 -0
  284. package/dist/utils/project.js.map +1 -0
  285. package/dist/utils/project.spec.d.ts +2 -0
  286. package/dist/utils/project.spec.d.ts.map +1 -0
  287. package/dist/utils/project.spec.js +222 -0
  288. package/dist/utils/project.spec.js.map +1 -0
  289. package/dist/utils/s3-upload.d.ts +43 -0
  290. package/dist/utils/s3-upload.d.ts.map +1 -0
  291. package/dist/utils/s3-upload.js +128 -0
  292. package/dist/utils/s3-upload.js.map +1 -0
  293. package/dist/utils/s3-upload.spec.d.ts +2 -0
  294. package/dist/utils/s3-upload.spec.d.ts.map +1 -0
  295. package/dist/utils/s3-upload.spec.js +140 -0
  296. package/dist/utils/s3-upload.spec.js.map +1 -0
  297. package/dist/utils/token-display.d.ts +14 -0
  298. package/dist/utils/token-display.d.ts.map +1 -0
  299. package/dist/utils/token-display.js +62 -0
  300. package/dist/utils/token-display.js.map +1 -0
  301. package/dist/utils/typescript-validator.d.ts +11 -0
  302. package/dist/utils/typescript-validator.d.ts.map +1 -0
  303. package/dist/utils/typescript-validator.js +58 -0
  304. package/dist/utils/typescript-validator.js.map +1 -0
  305. package/package.json +69 -0
@@ -0,0 +1,296 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { createDeployment, createTenant, getTenantDetails, listTenants, performDeployment } from './control-plane.js';
3
+ describe('control-plane API (mocked)', ()=>{
4
+ beforeEach(()=>{
5
+ vi.clearAllMocks();
6
+ });
7
+ afterEach(()=>{
8
+ vi.restoreAllMocks();
9
+ });
10
+ describe('listTenants', ()=>{
11
+ it('should return array of tenant instances', async ()=>{
12
+ const tenants = await listTenants('mock-access-token');
13
+ expect(Array.isArray(tenants)).toBe(true);
14
+ expect(tenants.length).toBeGreaterThan(0);
15
+ });
16
+ it('should return tenants with required fields', async ()=>{
17
+ const tenants = await listTenants('mock-access-token');
18
+ tenants.forEach((tenant)=>{
19
+ expect(tenant).toHaveProperty('id');
20
+ expect(tenant).toHaveProperty('domain');
21
+ expect(tenant).toHaveProperty('status');
22
+ expect(tenant).toHaveProperty('parentType');
23
+ expect(tenant).toHaveProperty('parentId');
24
+ expect(tenant).toHaveProperty('createdAt');
25
+ });
26
+ });
27
+ it('should return tenants with valid status', async ()=>{
28
+ const tenants = await listTenants('mock-access-token');
29
+ tenants.forEach((tenant)=>{
30
+ expect([
31
+ 'active',
32
+ 'provisioning',
33
+ 'inactive'
34
+ ]).toContain(tenant.status);
35
+ });
36
+ });
37
+ it('should return tenants with valid parentType', async ()=>{
38
+ const tenants = await listTenants('mock-access-token');
39
+ tenants.forEach((tenant)=>{
40
+ expect([
41
+ 'team',
42
+ 'org',
43
+ 'workspace'
44
+ ]).toContain(tenant.parentType);
45
+ });
46
+ });
47
+ it('should handle different access tokens', async ()=>{
48
+ const tenants1 = await listTenants('token-1');
49
+ const tenants2 = await listTenants('token-2');
50
+ // Mock should return same structure regardless of token
51
+ expect(tenants1.length).toBe(tenants2.length);
52
+ });
53
+ });
54
+ describe('createTenant', ()=>{
55
+ it('should create tenant with name', async ()=>{
56
+ const tenant = await createTenant('mock-access-token', {
57
+ name: 'test-cms'
58
+ });
59
+ expect(tenant).toHaveProperty('id');
60
+ expect(tenant).toHaveProperty('domain');
61
+ expect(tenant.domain).toContain('test-cms');
62
+ });
63
+ it('should return tenant with provisioning status', async ()=>{
64
+ const tenant = await createTenant('mock-access-token', {
65
+ name: 'new-cms'
66
+ });
67
+ expect(tenant.status).toBe('provisioning');
68
+ });
69
+ it('should generate unique tenant IDs', async ()=>{
70
+ const tenant1 = await createTenant('mock-access-token', {
71
+ name: 'cms-1'
72
+ });
73
+ const tenant2 = await createTenant('mock-access-token', {
74
+ name: 'cms-2'
75
+ });
76
+ expect(tenant1.id).not.toBe(tenant2.id);
77
+ });
78
+ it('should handle names with special characters', async ()=>{
79
+ const tenant = await createTenant('mock-access-token', {
80
+ name: 'My Test CMS 123!'
81
+ });
82
+ // Should convert to slug format
83
+ expect(tenant.domain).toMatch(/^[a-z0-9-]+\.figmacms\.com$/);
84
+ });
85
+ it('should include optional parent fields', async ()=>{
86
+ const tenant = await createTenant('mock-access-token', {
87
+ name: 'test-cms',
88
+ parentId: 'org_custom',
89
+ parentType: 'org'
90
+ });
91
+ expect(tenant.parentType).toBe('org');
92
+ expect(tenant.parentId).toBe('org_custom');
93
+ });
94
+ it('should default to team parent type', async ()=>{
95
+ const tenant = await createTenant('mock-access-token', {
96
+ name: 'test-cms'
97
+ });
98
+ expect(tenant.parentType).toBe('team');
99
+ });
100
+ it('should include createdAt timestamp', async ()=>{
101
+ const tenant = await createTenant('mock-access-token', {
102
+ name: 'test-cms'
103
+ });
104
+ expect(tenant.createdAt).toBeDefined();
105
+ expect(new Date(tenant.createdAt).getTime()).toBeGreaterThan(0);
106
+ });
107
+ });
108
+ describe('getTenantDetails', ()=>{
109
+ it('should return tenant details for given ID', async ()=>{
110
+ const tenant = await getTenantDetails('mock-access-token', 'tenant_abc123');
111
+ expect(tenant).toHaveProperty('id');
112
+ expect(tenant.id).toBe('tenant_abc123');
113
+ });
114
+ it('should return tenant with all required fields', async ()=>{
115
+ const tenant = await getTenantDetails('mock-access-token', 'tenant_test');
116
+ expect(tenant).toHaveProperty('id');
117
+ expect(tenant).toHaveProperty('domain');
118
+ expect(tenant).toHaveProperty('status');
119
+ expect(tenant).toHaveProperty('parentType');
120
+ expect(tenant).toHaveProperty('parentId');
121
+ expect(tenant).toHaveProperty('createdAt');
122
+ });
123
+ it('should return active status for mock tenant', async ()=>{
124
+ const tenant = await getTenantDetails('mock-access-token', 'tenant_test');
125
+ expect(tenant.status).toBe('active');
126
+ });
127
+ it('should handle different tenant IDs', async ()=>{
128
+ const tenant1 = await getTenantDetails('mock-access-token', 'tenant_1');
129
+ const tenant2 = await getTenantDetails('mock-access-token', 'tenant_2');
130
+ expect(tenant1.id).toBe('tenant_1');
131
+ expect(tenant2.id).toBe('tenant_2');
132
+ });
133
+ });
134
+ describe('createDeployment', ()=>{
135
+ it('should return deployment ID and upload URLs', async ()=>{
136
+ const result = await createDeployment('mock-token', 'tenant_123', {
137
+ staticAssets: [
138
+ '_next/static/abc.js',
139
+ '_next/static/xyz.css'
140
+ ]
141
+ });
142
+ expect(result).toHaveProperty('deploymentId');
143
+ expect(result).toHaveProperty('codeUploadUrl');
144
+ expect(result).toHaveProperty('staticAssetUploadUrls');
145
+ expect(result.deploymentId).toMatch(/^deploy_/);
146
+ });
147
+ it('should include upload URL for each static asset', async ()=>{
148
+ const result = await createDeployment('mock-token', 'tenant_456', {
149
+ staticAssets: [
150
+ '_next/static/abc.js',
151
+ '_next/static/xyz.css'
152
+ ]
153
+ });
154
+ expect(result.staticAssetUploadUrls).toHaveProperty('_next/static/abc.js');
155
+ expect(result.staticAssetUploadUrls).toHaveProperty('_next/static/xyz.css');
156
+ expect(result.staticAssetUploadUrls['_next/static/abc.js']).toContain('https://');
157
+ });
158
+ it('should return S3 upload URL format', async ()=>{
159
+ const result = await createDeployment('mock-token', 'tenant_789', {
160
+ staticAssets: [
161
+ '_next/static/test.js'
162
+ ]
163
+ });
164
+ expect(result.codeUploadUrl).toMatch(/^https:\/\/.*\.s3\..*\.amazonaws\.com\//);
165
+ expect(result.staticAssetUploadUrls['_next/static/test.js']).toMatch(/^https:\/\/.*\.s3\..*\.amazonaws\.com\//);
166
+ });
167
+ it('should handle empty static assets array', async ()=>{
168
+ const result = await createDeployment('mock-token', 'tenant_empty', {
169
+ staticAssets: []
170
+ });
171
+ expect(result.deploymentId).toBeDefined();
172
+ expect(result.codeUploadUrl).toBeDefined();
173
+ expect(Object.keys(result.staticAssetUploadUrls)).toHaveLength(0);
174
+ });
175
+ it('should generate unique deployment IDs', async ()=>{
176
+ const result1 = await createDeployment('mock-token', 'tenant_abc', {
177
+ staticAssets: []
178
+ });
179
+ const result2 = await createDeployment('mock-token', 'tenant_abc', {
180
+ staticAssets: []
181
+ });
182
+ expect(result1.deploymentId).not.toBe(result2.deploymentId);
183
+ });
184
+ });
185
+ describe('performDeployment', ()=>{
186
+ it('should deploy using deployment ID', async ()=>{
187
+ const result = await performDeployment('mock-token', 'tenant_123', {
188
+ deploymentId: 'deploy_abc123'
189
+ });
190
+ expect(result).toHaveProperty('deploymentId');
191
+ expect(result).toHaveProperty('message');
192
+ expect(result.deploymentId).toBe('deploy_abc123');
193
+ });
194
+ it('should return success message', async ()=>{
195
+ const result = await performDeployment('mock-token', 'tenant_456', {
196
+ deploymentId: 'deploy_xyz789'
197
+ });
198
+ expect(result.message).toBeDefined();
199
+ expect(result.message).toBe('Deployment initiated successfully');
200
+ expect(result.message.length).toBeGreaterThan(0);
201
+ });
202
+ it('should handle different tenant IDs', async ()=>{
203
+ const result1 = await performDeployment('mock-token', 'tenant_aaa', {
204
+ deploymentId: 'deploy_1'
205
+ });
206
+ const result2 = await performDeployment('mock-token', 'tenant_bbb', {
207
+ deploymentId: 'deploy_2'
208
+ });
209
+ expect(result1.deploymentId).toBe('deploy_1');
210
+ expect(result2.deploymentId).toBe('deploy_2');
211
+ });
212
+ });
213
+ describe('error handling', ()=>{
214
+ beforeEach(()=>{
215
+ // Force real API mode for error testing
216
+ process.env.FIGMA_MOCK_CONTROL_PLANE = 'false';
217
+ });
218
+ afterEach(()=>{
219
+ // Restore mock mode
220
+ delete process.env.FIGMA_MOCK_CONTROL_PLANE;
221
+ });
222
+ it('createDeployment should throw ControlPlaneError on API failure', async ()=>{
223
+ // Mock fetch to return error
224
+ global.fetch = vi.fn().mockResolvedValue({
225
+ ok: false,
226
+ status: 500,
227
+ statusText: 'Internal Server Error'
228
+ });
229
+ await expect(createDeployment('bad-token', 'tenant_fail', {
230
+ staticAssets: []
231
+ })).rejects.toThrow('Failed to create deployment: 500 Internal Server Error');
232
+ });
233
+ it('performDeployment should throw ControlPlaneError on API failure', async ()=>{
234
+ // Mock fetch to return error
235
+ global.fetch = vi.fn().mockResolvedValue({
236
+ ok: false,
237
+ status: 404,
238
+ statusText: 'Not Found'
239
+ });
240
+ await expect(performDeployment('bad-token', 'tenant_missing', {
241
+ deploymentId: 'deploy_notfound'
242
+ })).rejects.toThrow('Failed to perform deployment: 404 Not Found');
243
+ });
244
+ it('createDeployment should map snake_case API response to camelCase', async ()=>{
245
+ // Mock fetch to return successful API response with snake_case
246
+ global.fetch = vi.fn().mockResolvedValue({
247
+ json: ()=>({
248
+ error: false,
249
+ meta: {
250
+ code_upload_url: 'https://s3.amazonaws.com/code/upload',
251
+ deployment_id: 'deploy_test123',
252
+ static_asset_upload_urls: {
253
+ '_next/static/test.js': 'https://s3.amazonaws.com/static/test.js'
254
+ }
255
+ },
256
+ status: 200
257
+ }),
258
+ ok: true
259
+ });
260
+ const result = await createDeployment('token', 'tenant_123', {
261
+ staticAssets: [
262
+ '_next/static/test.js'
263
+ ]
264
+ });
265
+ // Verify response is mapped to camelCase
266
+ expect(result.deploymentId).toBe('deploy_test123');
267
+ expect(result.codeUploadUrl).toBe('https://s3.amazonaws.com/code/upload');
268
+ expect(result.staticAssetUploadUrls).toEqual({
269
+ '_next/static/test.js': 'https://s3.amazonaws.com/static/test.js'
270
+ });
271
+ });
272
+ it('performDeployment should map snake_case API response to camelCase', async ()=>{
273
+ // Mock fetch to return successful API response with snake_case
274
+ global.fetch = vi.fn().mockResolvedValue({
275
+ json: ()=>({
276
+ error: false,
277
+ meta: {
278
+ deployment_id: 'deploy_abc456',
279
+ message: 'Deployment successful',
280
+ status: 'deployed'
281
+ },
282
+ status: 200
283
+ }),
284
+ ok: true
285
+ });
286
+ const result = await performDeployment('token', 'tenant_456', {
287
+ deploymentId: 'deploy_abc456'
288
+ });
289
+ // Verify response is mapped to camelCase
290
+ expect(result.deploymentId).toBe('deploy_abc456');
291
+ expect(result.message).toBe('Deployment successful');
292
+ });
293
+ });
294
+ });
295
+
296
+ //# sourceMappingURL=control-plane.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/api/control-plane.spec.ts"],"sourcesContent":["import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport {\n createDeployment,\n createTenant,\n getTenantDetails,\n listTenants,\n performDeployment,\n} from './control-plane.js'\n\ndescribe('control-plane API (mocked)', () => {\n beforeEach(() => {\n vi.clearAllMocks()\n })\n\n afterEach(() => {\n vi.restoreAllMocks()\n })\n\n describe('listTenants', () => {\n it('should return array of tenant instances', async () => {\n const tenants = await listTenants('mock-access-token')\n\n expect(Array.isArray(tenants)).toBe(true)\n expect(tenants.length).toBeGreaterThan(0)\n })\n\n it('should return tenants with required fields', async () => {\n const tenants = await listTenants('mock-access-token')\n\n tenants.forEach((tenant) => {\n expect(tenant).toHaveProperty('id')\n expect(tenant).toHaveProperty('domain')\n expect(tenant).toHaveProperty('status')\n expect(tenant).toHaveProperty('parentType')\n expect(tenant).toHaveProperty('parentId')\n expect(tenant).toHaveProperty('createdAt')\n })\n })\n\n it('should return tenants with valid status', async () => {\n const tenants = await listTenants('mock-access-token')\n\n tenants.forEach((tenant) => {\n expect(['active', 'provisioning', 'inactive']).toContain(tenant.status)\n })\n })\n\n it('should return tenants with valid parentType', async () => {\n const tenants = await listTenants('mock-access-token')\n\n tenants.forEach((tenant) => {\n expect(['team', 'org', 'workspace']).toContain(tenant.parentType)\n })\n })\n\n it('should handle different access tokens', async () => {\n const tenants1 = await listTenants('token-1')\n const tenants2 = await listTenants('token-2')\n\n // Mock should return same structure regardless of token\n expect(tenants1.length).toBe(tenants2.length)\n })\n })\n\n describe('createTenant', () => {\n it('should create tenant with name', async () => {\n const tenant = await createTenant('mock-access-token', {\n name: 'test-cms',\n })\n\n expect(tenant).toHaveProperty('id')\n expect(tenant).toHaveProperty('domain')\n expect(tenant.domain).toContain('test-cms')\n })\n\n it('should return tenant with provisioning status', async () => {\n const tenant = await createTenant('mock-access-token', {\n name: 'new-cms',\n })\n\n expect(tenant.status).toBe('provisioning')\n })\n\n it('should generate unique tenant IDs', async () => {\n const tenant1 = await createTenant('mock-access-token', {\n name: 'cms-1',\n })\n const tenant2 = await createTenant('mock-access-token', {\n name: 'cms-2',\n })\n\n expect(tenant1.id).not.toBe(tenant2.id)\n })\n\n it('should handle names with special characters', async () => {\n const tenant = await createTenant('mock-access-token', {\n name: 'My Test CMS 123!',\n })\n\n // Should convert to slug format\n expect(tenant.domain).toMatch(/^[a-z0-9-]+\\.figmacms\\.com$/)\n })\n\n it('should include optional parent fields', async () => {\n const tenant = await createTenant('mock-access-token', {\n name: 'test-cms',\n parentId: 'org_custom',\n parentType: 'org',\n })\n\n expect(tenant.parentType).toBe('org')\n expect(tenant.parentId).toBe('org_custom')\n })\n\n it('should default to team parent type', async () => {\n const tenant = await createTenant('mock-access-token', {\n name: 'test-cms',\n })\n\n expect(tenant.parentType).toBe('team')\n })\n\n it('should include createdAt timestamp', async () => {\n const tenant = await createTenant('mock-access-token', {\n name: 'test-cms',\n })\n\n expect(tenant.createdAt).toBeDefined()\n expect(new Date(tenant.createdAt!).getTime()).toBeGreaterThan(0)\n })\n })\n\n describe('getTenantDetails', () => {\n it('should return tenant details for given ID', async () => {\n const tenant = await getTenantDetails('mock-access-token', 'tenant_abc123')\n\n expect(tenant).toHaveProperty('id')\n expect(tenant.id).toBe('tenant_abc123')\n })\n\n it('should return tenant with all required fields', async () => {\n const tenant = await getTenantDetails('mock-access-token', 'tenant_test')\n\n expect(tenant).toHaveProperty('id')\n expect(tenant).toHaveProperty('domain')\n expect(tenant).toHaveProperty('status')\n expect(tenant).toHaveProperty('parentType')\n expect(tenant).toHaveProperty('parentId')\n expect(tenant).toHaveProperty('createdAt')\n })\n\n it('should return active status for mock tenant', async () => {\n const tenant = await getTenantDetails('mock-access-token', 'tenant_test')\n\n expect(tenant.status).toBe('active')\n })\n\n it('should handle different tenant IDs', async () => {\n const tenant1 = await getTenantDetails('mock-access-token', 'tenant_1')\n const tenant2 = await getTenantDetails('mock-access-token', 'tenant_2')\n\n expect(tenant1.id).toBe('tenant_1')\n expect(tenant2.id).toBe('tenant_2')\n })\n })\n\n describe('createDeployment', () => {\n it('should return deployment ID and upload URLs', async () => {\n const result = await createDeployment('mock-token', 'tenant_123', {\n staticAssets: ['_next/static/abc.js', '_next/static/xyz.css'],\n })\n\n expect(result).toHaveProperty('deploymentId')\n expect(result).toHaveProperty('codeUploadUrl')\n expect(result).toHaveProperty('staticAssetUploadUrls')\n expect(result.deploymentId).toMatch(/^deploy_/)\n })\n\n it('should include upload URL for each static asset', async () => {\n const result = await createDeployment('mock-token', 'tenant_456', {\n staticAssets: ['_next/static/abc.js', '_next/static/xyz.css'],\n })\n\n expect(result.staticAssetUploadUrls).toHaveProperty('_next/static/abc.js')\n expect(result.staticAssetUploadUrls).toHaveProperty('_next/static/xyz.css')\n expect(result.staticAssetUploadUrls['_next/static/abc.js']).toContain('https://')\n })\n\n it('should return S3 upload URL format', async () => {\n const result = await createDeployment('mock-token', 'tenant_789', {\n staticAssets: ['_next/static/test.js'],\n })\n\n expect(result.codeUploadUrl).toMatch(/^https:\\/\\/.*\\.s3\\..*\\.amazonaws\\.com\\//)\n expect(result.staticAssetUploadUrls['_next/static/test.js']).toMatch(\n /^https:\\/\\/.*\\.s3\\..*\\.amazonaws\\.com\\//,\n )\n })\n\n it('should handle empty static assets array', async () => {\n const result = await createDeployment('mock-token', 'tenant_empty', {\n staticAssets: [],\n })\n\n expect(result.deploymentId).toBeDefined()\n expect(result.codeUploadUrl).toBeDefined()\n expect(Object.keys(result.staticAssetUploadUrls)).toHaveLength(0)\n })\n\n it('should generate unique deployment IDs', async () => {\n const result1 = await createDeployment('mock-token', 'tenant_abc', {\n staticAssets: [],\n })\n const result2 = await createDeployment('mock-token', 'tenant_abc', {\n staticAssets: [],\n })\n\n expect(result1.deploymentId).not.toBe(result2.deploymentId)\n })\n })\n\n describe('performDeployment', () => {\n it('should deploy using deployment ID', async () => {\n const result = await performDeployment('mock-token', 'tenant_123', {\n deploymentId: 'deploy_abc123',\n })\n\n expect(result).toHaveProperty('deploymentId')\n expect(result).toHaveProperty('message')\n expect(result.deploymentId).toBe('deploy_abc123')\n })\n\n it('should return success message', async () => {\n const result = await performDeployment('mock-token', 'tenant_456', {\n deploymentId: 'deploy_xyz789',\n })\n\n expect(result.message).toBeDefined()\n expect(result.message).toBe('Deployment initiated successfully')\n expect(result.message.length).toBeGreaterThan(0)\n })\n\n it('should handle different tenant IDs', async () => {\n const result1 = await performDeployment('mock-token', 'tenant_aaa', {\n deploymentId: 'deploy_1',\n })\n const result2 = await performDeployment('mock-token', 'tenant_bbb', {\n deploymentId: 'deploy_2',\n })\n\n expect(result1.deploymentId).toBe('deploy_1')\n expect(result2.deploymentId).toBe('deploy_2')\n })\n })\n\n describe('error handling', () => {\n beforeEach(() => {\n // Force real API mode for error testing\n process.env.FIGMA_MOCK_CONTROL_PLANE = 'false'\n })\n\n afterEach(() => {\n // Restore mock mode\n delete process.env.FIGMA_MOCK_CONTROL_PLANE\n })\n\n it('createDeployment should throw ControlPlaneError on API failure', async () => {\n // Mock fetch to return error\n global.fetch = vi.fn().mockResolvedValue({\n ok: false,\n status: 500,\n statusText: 'Internal Server Error',\n })\n\n await expect(\n createDeployment('bad-token', 'tenant_fail', { staticAssets: [] }),\n ).rejects.toThrow('Failed to create deployment: 500 Internal Server Error')\n })\n\n it('performDeployment should throw ControlPlaneError on API failure', async () => {\n // Mock fetch to return error\n global.fetch = vi.fn().mockResolvedValue({\n ok: false,\n status: 404,\n statusText: 'Not Found',\n })\n\n await expect(\n performDeployment('bad-token', 'tenant_missing', {\n deploymentId: 'deploy_notfound',\n }),\n ).rejects.toThrow('Failed to perform deployment: 404 Not Found')\n })\n\n it('createDeployment should map snake_case API response to camelCase', async () => {\n // Mock fetch to return successful API response with snake_case\n global.fetch = vi.fn().mockResolvedValue({\n json: () => ({\n error: false,\n meta: {\n code_upload_url: 'https://s3.amazonaws.com/code/upload',\n deployment_id: 'deploy_test123',\n static_asset_upload_urls: {\n '_next/static/test.js': 'https://s3.amazonaws.com/static/test.js',\n },\n },\n status: 200,\n }),\n ok: true,\n })\n\n const result = await createDeployment('token', 'tenant_123', {\n staticAssets: ['_next/static/test.js'],\n })\n\n // Verify response is mapped to camelCase\n expect(result.deploymentId).toBe('deploy_test123')\n expect(result.codeUploadUrl).toBe('https://s3.amazonaws.com/code/upload')\n expect(result.staticAssetUploadUrls).toEqual({\n '_next/static/test.js': 'https://s3.amazonaws.com/static/test.js',\n })\n })\n\n it('performDeployment should map snake_case API response to camelCase', async () => {\n // Mock fetch to return successful API response with snake_case\n global.fetch = vi.fn().mockResolvedValue({\n json: () => ({\n error: false,\n meta: {\n deployment_id: 'deploy_abc456',\n message: 'Deployment successful',\n status: 'deployed',\n },\n status: 200,\n }),\n ok: true,\n })\n\n const result = await performDeployment('token', 'tenant_456', {\n deploymentId: 'deploy_abc456',\n })\n\n // Verify response is mapped to camelCase\n expect(result.deploymentId).toBe('deploy_abc456')\n expect(result.message).toBe('Deployment successful')\n })\n })\n})\n"],"names":["afterEach","beforeEach","describe","expect","it","vi","createDeployment","createTenant","getTenantDetails","listTenants","performDeployment","clearAllMocks","restoreAllMocks","tenants","Array","isArray","toBe","length","toBeGreaterThan","forEach","tenant","toHaveProperty","toContain","status","parentType","tenants1","tenants2","name","domain","tenant1","tenant2","id","not","toMatch","parentId","createdAt","toBeDefined","Date","getTime","result","staticAssets","deploymentId","staticAssetUploadUrls","codeUploadUrl","Object","keys","toHaveLength","result1","result2","message","process","env","FIGMA_MOCK_CONTROL_PLANE","global","fetch","fn","mockResolvedValue","ok","statusText","rejects","toThrow","json","error","meta","code_upload_url","deployment_id","static_asset_upload_urls","toEqual"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Figma API Client
3
+ *
4
+ * Client for Figma REST API (Pixie) endpoints
5
+ * Handles project token generation and other CMS-related API calls
6
+ */
7
+ import type { ProjectToken } from '../auth/types.js';
8
+ /**
9
+ * Error thrown when Figma API calls fail
10
+ */
11
+ export declare class FigmaApiError extends Error {
12
+ statusCode?: number | undefined;
13
+ cause?: Error | undefined;
14
+ constructor(message: string, statusCode?: number | undefined, cause?: Error | undefined);
15
+ }
16
+ /**
17
+ * Get a project token (JWT) for a specific tenant/CMS
18
+ *
19
+ * This token is used by local Payload instances to authenticate
20
+ * requests to the Content API. The token contains the CMS ID and
21
+ * user identifying information in its claims.
22
+ *
23
+ * Endpoint: GET https://api.figma.com/v1/cms/:id/token/
24
+ *
25
+ * @param accessToken - OAuth access token for authentication
26
+ * @param tenantId - The tenant/CMS ID
27
+ * @returns ProjectToken with JWT and expiry information
28
+ * @throws {FigmaApiError} If the API call fails
29
+ */
30
+ export declare function getProjectToken(accessToken: string, tenantId: string): Promise<ProjectToken>;
31
+ //# sourceMappingURL=figma-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-api.d.ts","sourceRoot":"","sources":["../../src/api/figma-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAwB,MAAM,kBAAkB,CAAA;AAI1E;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAG7B,UAAU,CAAC,EAAE,MAAM;IACnB,KAAK,CAAC,EAAE,KAAK;gBAFpB,OAAO,EAAE,MAAM,EACR,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,KAAK,CAAC,EAAE,KAAK,YAAA;CAKvB;AAmBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAuFvB"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Figma API Client
3
+ *
4
+ * Client for Figma REST API (Pixie) endpoints
5
+ * Handles project token generation and other CMS-related API calls
6
+ */ import * as log from '../utils/log.js';
7
+ /**
8
+ * Error thrown when Figma API calls fail
9
+ */ export class FigmaApiError extends Error {
10
+ statusCode;
11
+ cause;
12
+ constructor(message, statusCode, cause){
13
+ super(message), this.statusCode = statusCode, this.cause = cause;
14
+ this.name = 'FigmaApiError';
15
+ }
16
+ }
17
+ /**
18
+ * Base URLs for Figma API
19
+ */ const API_BASE_URLS = {
20
+ prod: 'https://api.figma.com',
21
+ staging: 'https://api.staging.figma.com'
22
+ };
23
+ /**
24
+ * Get the Figma API base URL based on environment
25
+ * TODO: Use this once real API endpoints are available
26
+ */ function _getApiBaseUrl() {
27
+ // For now, default to staging to match OAuth config
28
+ return process.env.FIGMA_API_BASE_URL || API_BASE_URLS.staging;
29
+ }
30
+ /**
31
+ * Get a project token (JWT) for a specific tenant/CMS
32
+ *
33
+ * This token is used by local Payload instances to authenticate
34
+ * requests to the Content API. The token contains the CMS ID and
35
+ * user identifying information in its claims.
36
+ *
37
+ * Endpoint: GET https://api.figma.com/v1/cms/:id/token/
38
+ *
39
+ * @param accessToken - OAuth access token for authentication
40
+ * @param tenantId - The tenant/CMS ID
41
+ * @returns ProjectToken with JWT and expiry information
42
+ * @throws {FigmaApiError} If the API call fails
43
+ */ export async function getProjectToken(accessToken, tenantId) {
44
+ // Check if project token API should be mocked
45
+ // Default to true until real API is available
46
+ const shouldMock = process.env.FIGMA_MOCK_PROJECT_TOKEN !== 'false';
47
+ try {
48
+ if (shouldMock) {
49
+ // MOCK IMPLEMENTATION: Generate a mock JWT token for testing
50
+ log.debug('Using mock project token API for getProjectToken (FIGMA_MOCK_PROJECT_TOKEN != "false")');
51
+ const mockToken = createMockJWT(tenantId, 25) // 25 minutes expiry
52
+ ;
53
+ // Mock response data
54
+ const data = {
55
+ meta: {
56
+ token: mockToken
57
+ }
58
+ };
59
+ if (!data.meta?.token) {
60
+ throw new FigmaApiError('Invalid response: missing token in meta');
61
+ }
62
+ // Parse the JWT to extract expiry (it's in the payload)
63
+ const expiresAt = parseJwtExpiry(data.meta.token);
64
+ return {
65
+ expiresAt,
66
+ tenantId,
67
+ token: data.meta.token
68
+ };
69
+ }
70
+ // REAL API IMPLEMENTATION
71
+ const baseUrl = _getApiBaseUrl();
72
+ const url = `${baseUrl}/v1/cms/${tenantId}/token/`;
73
+ const response = await fetch(url, {
74
+ headers: {
75
+ Authorization: `Bearer ${accessToken}`
76
+ },
77
+ method: 'POST'
78
+ });
79
+ if (!response.ok) {
80
+ // Handle error responses
81
+ let errorMessage = `Failed to get project token: ${response.status} ${response.statusText}`;
82
+ try {
83
+ const errorBody = await response.json();
84
+ if (errorBody.error || errorBody.message) {
85
+ errorMessage = errorBody.error || errorBody.message;
86
+ }
87
+ } catch {
88
+ // Ignore JSON parse errors for error body
89
+ }
90
+ throw new FigmaApiError(errorMessage, response.status);
91
+ }
92
+ const data = await response.json();
93
+ if (!data.meta?.token) {
94
+ throw new FigmaApiError('Invalid response: missing token in meta');
95
+ }
96
+ // Parse the JWT to extract expiry (it's in the payload)
97
+ const expiresAt = parseJwtExpiry(data.meta.token);
98
+ return {
99
+ expiresAt,
100
+ tenantId,
101
+ token: data.meta.token
102
+ };
103
+ } catch (error) {
104
+ if (error instanceof FigmaApiError) {
105
+ throw error;
106
+ }
107
+ // Handle network errors and other unexpected errors
108
+ throw new FigmaApiError(`Failed to get project token: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, error instanceof Error ? error : undefined);
109
+ }
110
+ }
111
+ /**
112
+ * Create a mock JWT token for testing
113
+ * TODO: Remove this when real API is available
114
+ */ function createMockJWT(tenantId, expiresInMinutes) {
115
+ const header = {
116
+ alg: 'RS256',
117
+ typ: 'JWT'
118
+ };
119
+ const now = Math.floor(Date.now() / 1000) // Current time in seconds
120
+ ;
121
+ const exp = now + expiresInMinutes * 60 // Expiry in seconds
122
+ ;
123
+ const payload = {
124
+ aud: tenantId,
125
+ email: 'developer@example.com',
126
+ exp,
127
+ iat: now,
128
+ sub: 'user123'
129
+ };
130
+ // Base64url encode
131
+ const encodeBase64Url = (obj)=>{
132
+ return Buffer.from(JSON.stringify(obj)).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
133
+ };
134
+ const headerEncoded = encodeBase64Url(header);
135
+ const payloadEncoded = encodeBase64Url(payload);
136
+ const signature = 'mock_signature_' + Math.random().toString(36).substring(2, 15);
137
+ return `${headerEncoded}.${payloadEncoded}.${signature}`;
138
+ }
139
+ /**
140
+ * Parse JWT expiry from token
141
+ * JWTs contain an 'exp' claim in the payload (second part)
142
+ *
143
+ * @param token - JWT token string
144
+ * @returns Unix timestamp in milliseconds when token expires
145
+ * @throws {FigmaApiError} If JWT cannot be parsed
146
+ */ function parseJwtExpiry(token) {
147
+ try {
148
+ // JWT format: header.payload.signature
149
+ const parts = token.split('.');
150
+ if (parts.length !== 3) {
151
+ throw new Error('Invalid JWT format');
152
+ }
153
+ // Decode the payload (second part)
154
+ const payload = parts[1];
155
+ // Use atob for base64url decoding (handle padding and URL-safe characters)
156
+ const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
157
+ const paddedBase64 = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, '=');
158
+ const decodedPayload = JSON.parse(Buffer.from(paddedBase64, 'base64').toString('utf-8'));
159
+ if (!decodedPayload.exp || typeof decodedPayload.exp !== 'number') {
160
+ throw new Error('JWT payload missing exp claim');
161
+ }
162
+ // JWT exp claim is in seconds, convert to milliseconds
163
+ return decodedPayload.exp * 1000;
164
+ } catch (error) {
165
+ throw new FigmaApiError(`Failed to parse JWT expiry: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, error instanceof Error ? error : undefined);
166
+ }
167
+ }
168
+
169
+ //# sourceMappingURL=figma-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/api/figma-api.ts"],"sourcesContent":["/**\n * Figma API Client\n *\n * Client for Figma REST API (Pixie) endpoints\n * Handles project token generation and other CMS-related API calls\n */\n\nimport type { ProjectToken, ProjectTokenResponse } from '../auth/types.js'\n\nimport * as log from '../utils/log.js'\n\n/**\n * Error thrown when Figma API calls fail\n */\nexport class FigmaApiError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public cause?: Error,\n ) {\n super(message)\n this.name = 'FigmaApiError'\n }\n}\n\n/**\n * Base URLs for Figma API\n */\nconst API_BASE_URLS = {\n prod: 'https://api.figma.com',\n staging: 'https://api.staging.figma.com',\n}\n\n/**\n * Get the Figma API base URL based on environment\n * TODO: Use this once real API endpoints are available\n */\nfunction _getApiBaseUrl(): string {\n // For now, default to staging to match OAuth config\n return process.env.FIGMA_API_BASE_URL || API_BASE_URLS.staging\n}\n\n/**\n * Get a project token (JWT) for a specific tenant/CMS\n *\n * This token is used by local Payload instances to authenticate\n * requests to the Content API. The token contains the CMS ID and\n * user identifying information in its claims.\n *\n * Endpoint: GET https://api.figma.com/v1/cms/:id/token/\n *\n * @param accessToken - OAuth access token for authentication\n * @param tenantId - The tenant/CMS ID\n * @returns ProjectToken with JWT and expiry information\n * @throws {FigmaApiError} If the API call fails\n */\nexport async function getProjectToken(\n accessToken: string,\n tenantId: string,\n): Promise<ProjectToken> {\n // Check if project token API should be mocked\n // Default to true until real API is available\n const shouldMock = process.env.FIGMA_MOCK_PROJECT_TOKEN !== 'false'\n\n try {\n if (shouldMock) {\n // MOCK IMPLEMENTATION: Generate a mock JWT token for testing\n log.debug(\n 'Using mock project token API for getProjectToken (FIGMA_MOCK_PROJECT_TOKEN != \"false\")',\n )\n const mockToken = createMockJWT(tenantId, 25) // 25 minutes expiry\n\n // Mock response data\n const data: ProjectTokenResponse = {\n meta: {\n token: mockToken,\n },\n }\n\n if (!data.meta?.token) {\n throw new FigmaApiError('Invalid response: missing token in meta')\n }\n\n // Parse the JWT to extract expiry (it's in the payload)\n const expiresAt = parseJwtExpiry(data.meta.token)\n\n return {\n expiresAt,\n tenantId,\n token: data.meta.token,\n }\n }\n\n // REAL API IMPLEMENTATION\n const baseUrl = _getApiBaseUrl()\n const url = `${baseUrl}/v1/cms/${tenantId}/token/`\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n // Handle error responses\n let errorMessage = `Failed to get project token: ${response.status} ${response.statusText}`\n\n try {\n const errorBody = await response.json()\n if (errorBody.error || errorBody.message) {\n errorMessage = errorBody.error || errorBody.message\n }\n } catch {\n // Ignore JSON parse errors for error body\n }\n\n throw new FigmaApiError(errorMessage, response.status)\n }\n\n const data: ProjectTokenResponse = await response.json()\n\n if (!data.meta?.token) {\n throw new FigmaApiError('Invalid response: missing token in meta')\n }\n\n // Parse the JWT to extract expiry (it's in the payload)\n const expiresAt = parseJwtExpiry(data.meta.token)\n\n return {\n expiresAt,\n tenantId,\n token: data.meta.token,\n }\n } catch (error) {\n if (error instanceof FigmaApiError) {\n throw error\n }\n\n // Handle network errors and other unexpected errors\n throw new FigmaApiError(\n `Failed to get project token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n undefined,\n error instanceof Error ? error : undefined,\n )\n }\n}\n\n/**\n * Create a mock JWT token for testing\n * TODO: Remove this when real API is available\n */\nfunction createMockJWT(tenantId: string, expiresInMinutes: number): string {\n const header = {\n alg: 'RS256',\n typ: 'JWT',\n }\n\n const now = Math.floor(Date.now() / 1000) // Current time in seconds\n const exp = now + expiresInMinutes * 60 // Expiry in seconds\n\n const payload = {\n aud: tenantId, // Audience claim contains the CMS ID\n email: 'developer@example.com',\n exp,\n iat: now,\n sub: 'user123',\n }\n\n // Base64url encode\n const encodeBase64Url = (obj: object): string => {\n return Buffer.from(JSON.stringify(obj))\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n }\n\n const headerEncoded = encodeBase64Url(header)\n const payloadEncoded = encodeBase64Url(payload)\n const signature = 'mock_signature_' + Math.random().toString(36).substring(2, 15)\n\n return `${headerEncoded}.${payloadEncoded}.${signature}`\n}\n\n/**\n * Parse JWT expiry from token\n * JWTs contain an 'exp' claim in the payload (second part)\n *\n * @param token - JWT token string\n * @returns Unix timestamp in milliseconds when token expires\n * @throws {FigmaApiError} If JWT cannot be parsed\n */\nfunction parseJwtExpiry(token: string): number {\n try {\n // JWT format: header.payload.signature\n const parts = token.split('.')\n if (parts.length !== 3) {\n throw new Error('Invalid JWT format')\n }\n\n // Decode the payload (second part)\n const payload = parts[1]\n // Use atob for base64url decoding (handle padding and URL-safe characters)\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')\n const paddedBase64 = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')\n\n const decodedPayload = JSON.parse(Buffer.from(paddedBase64, 'base64').toString('utf-8')) as {\n exp?: number\n }\n\n if (!decodedPayload.exp || typeof decodedPayload.exp !== 'number') {\n throw new Error('JWT payload missing exp claim')\n }\n\n // JWT exp claim is in seconds, convert to milliseconds\n return decodedPayload.exp * 1000\n } catch (error) {\n throw new FigmaApiError(\n `Failed to parse JWT expiry: ${error instanceof Error ? error.message : 'Unknown error'}`,\n undefined,\n error instanceof Error ? error : undefined,\n )\n }\n}\n"],"names":["log","FigmaApiError","Error","message","statusCode","cause","name","API_BASE_URLS","prod","staging","_getApiBaseUrl","process","env","FIGMA_API_BASE_URL","getProjectToken","accessToken","tenantId","shouldMock","FIGMA_MOCK_PROJECT_TOKEN","debug","mockToken","createMockJWT","data","meta","token","expiresAt","parseJwtExpiry","baseUrl","url","response","fetch","headers","Authorization","method","ok","errorMessage","status","statusText","errorBody","json","error","undefined","expiresInMinutes","header","alg","typ","now","Math","floor","Date","exp","payload","aud","email","iat","sub","encodeBase64Url","obj","Buffer","from","JSON","stringify","toString","replace","headerEncoded","payloadEncoded","signature","random","substring","parts","split","length","base64","paddedBase64","padEnd","decodedPayload","parse"],"mappings":"AAAA;;;;;CAKC,GAID,YAAYA,SAAS,kBAAiB;AAEtC;;CAEC,GACD,OAAO,MAAMC,sBAAsBC;;;IACjC,YACEC,OAAe,EACf,AAAOC,UAAmB,EAC1B,AAAOC,KAAa,CACpB;QACA,KAAK,CAACF,eAHCC,aAAAA,iBACAC,QAAAA;QAGP,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA;;CAEC,GACD,MAAMC,gBAAgB;IACpBC,MAAM;IACNC,SAAS;AACX;AAEA;;;CAGC,GACD,SAASC;IACP,oDAAoD;IACpD,OAAOC,QAAQC,GAAG,CAACC,kBAAkB,IAAIN,cAAcE,OAAO;AAChE;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,eAAeK,gBACpBC,WAAmB,EACnBC,QAAgB;IAEhB,8CAA8C;IAC9C,8CAA8C;IAC9C,MAAMC,aAAaN,QAAQC,GAAG,CAACM,wBAAwB,KAAK;IAE5D,IAAI;QACF,IAAID,YAAY;YACd,6DAA6D;YAC7DjB,IAAImB,KAAK,CACP;YAEF,MAAMC,YAAYC,cAAcL,UAAU,IAAI,oBAAoB;;YAElE,qBAAqB;YACrB,MAAMM,OAA6B;gBACjCC,MAAM;oBACJC,OAAOJ;gBACT;YACF;YAEA,IAAI,CAACE,KAAKC,IAAI,EAAEC,OAAO;gBACrB,MAAM,IAAIvB,cAAc;YAC1B;YAEA,wDAAwD;YACxD,MAAMwB,YAAYC,eAAeJ,KAAKC,IAAI,CAACC,KAAK;YAEhD,OAAO;gBACLC;gBACAT;gBACAQ,OAAOF,KAAKC,IAAI,CAACC,KAAK;YACxB;QACF;QAEA,0BAA0B;QAC1B,MAAMG,UAAUjB;QAChB,MAAMkB,MAAM,GAAGD,QAAQ,QAAQ,EAAEX,SAAS,OAAO,CAAC;QAElD,MAAMa,WAAW,MAAMC,MAAMF,KAAK;YAChCG,SAAS;gBACPC,eAAe,CAAC,OAAO,EAAEjB,aAAa;YACxC;YACAkB,QAAQ;QACV;QAEA,IAAI,CAACJ,SAASK,EAAE,EAAE;YAChB,yBAAyB;YACzB,IAAIC,eAAe,CAAC,6BAA6B,EAAEN,SAASO,MAAM,CAAC,CAAC,EAAEP,SAASQ,UAAU,EAAE;YAE3F,IAAI;gBACF,MAAMC,YAAY,MAAMT,SAASU,IAAI;gBACrC,IAAID,UAAUE,KAAK,IAAIF,UAAUnC,OAAO,EAAE;oBACxCgC,eAAeG,UAAUE,KAAK,IAAIF,UAAUnC,OAAO;gBACrD;YACF,EAAE,OAAM;YACN,0CAA0C;YAC5C;YAEA,MAAM,IAAIF,cAAckC,cAAcN,SAASO,MAAM;QACvD;QAEA,MAAMd,OAA6B,MAAMO,SAASU,IAAI;QAEtD,IAAI,CAACjB,KAAKC,IAAI,EAAEC,OAAO;YACrB,MAAM,IAAIvB,cAAc;QAC1B;QAEA,wDAAwD;QACxD,MAAMwB,YAAYC,eAAeJ,KAAKC,IAAI,CAACC,KAAK;QAEhD,OAAO;YACLC;YACAT;YACAQ,OAAOF,KAAKC,IAAI,CAACC,KAAK;QACxB;IACF,EAAE,OAAOgB,OAAO;QACd,IAAIA,iBAAiBvC,eAAe;YAClC,MAAMuC;QACR;QAEA,oDAAoD;QACpD,MAAM,IAAIvC,cACR,CAAC,6BAA6B,EAAEuC,iBAAiBtC,QAAQsC,MAAMrC,OAAO,GAAG,iBAAiB,EAC1FsC,WACAD,iBAAiBtC,QAAQsC,QAAQC;IAErC;AACF;AAEA;;;CAGC,GACD,SAASpB,cAAcL,QAAgB,EAAE0B,gBAAwB;IAC/D,MAAMC,SAAS;QACbC,KAAK;QACLC,KAAK;IACP;IAEA,MAAMC,MAAMC,KAAKC,KAAK,CAACC,KAAKH,GAAG,KAAK,MAAM,0BAA0B;;IACpE,MAAMI,MAAMJ,MAAMJ,mBAAmB,GAAG,oBAAoB;;IAE5D,MAAMS,UAAU;QACdC,KAAKpC;QACLqC,OAAO;QACPH;QACAI,KAAKR;QACLS,KAAK;IACP;IAEA,mBAAmB;IACnB,MAAMC,kBAAkB,CAACC;QACvB,OAAOC,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACJ,MAC/BK,QAAQ,CAAC,UACTC,OAAO,CAAC,OAAO,KACfA,OAAO,CAAC,OAAO,KACfA,OAAO,CAAC,MAAM;IACnB;IAEA,MAAMC,gBAAgBR,gBAAgBb;IACtC,MAAMsB,iBAAiBT,gBAAgBL;IACvC,MAAMe,YAAY,oBAAoBnB,KAAKoB,MAAM,GAAGL,QAAQ,CAAC,IAAIM,SAAS,CAAC,GAAG;IAE9E,OAAO,GAAGJ,cAAc,CAAC,EAAEC,eAAe,CAAC,EAAEC,WAAW;AAC1D;AAEA;;;;;;;CAOC,GACD,SAASxC,eAAeF,KAAa;IACnC,IAAI;QACF,uCAAuC;QACvC,MAAM6C,QAAQ7C,MAAM8C,KAAK,CAAC;QAC1B,IAAID,MAAME,MAAM,KAAK,GAAG;YACtB,MAAM,IAAIrE,MAAM;QAClB;QAEA,mCAAmC;QACnC,MAAMiD,UAAUkB,KAAK,CAAC,EAAE;QACxB,2EAA2E;QAC3E,MAAMG,SAASrB,QAAQY,OAAO,CAAC,MAAM,KAAKA,OAAO,CAAC,MAAM;QACxD,MAAMU,eAAeD,OAAOE,MAAM,CAACF,OAAOD,MAAM,GAAI,AAAC,CAAA,IAAKC,OAAOD,MAAM,GAAG,CAAC,IAAK,GAAI;QAEpF,MAAMI,iBAAiBf,KAAKgB,KAAK,CAAClB,OAAOC,IAAI,CAACc,cAAc,UAAUX,QAAQ,CAAC;QAI/E,IAAI,CAACa,eAAezB,GAAG,IAAI,OAAOyB,eAAezB,GAAG,KAAK,UAAU;YACjE,MAAM,IAAIhD,MAAM;QAClB;QAEA,uDAAuD;QACvD,OAAOyE,eAAezB,GAAG,GAAG;IAC9B,EAAE,OAAOV,OAAO;QACd,MAAM,IAAIvC,cACR,CAAC,4BAA4B,EAAEuC,iBAAiBtC,QAAQsC,MAAMrC,OAAO,GAAG,iBAAiB,EACzFsC,WACAD,iBAAiBtC,QAAQsC,QAAQC;IAErC;AACF"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tests JWT parsing helpers and validates mock API contract.
3
+ *
4
+ * NOTE: The Figma API is currently mocked for development.
5
+ *
6
+ * These tests verify:
7
+ * 1. JWT parsing helper functions (our code)
8
+ * 2. JWT structure matches expected Figma API contract
9
+ * 3. Mock responses are consistent for development
10
+ *
11
+ * TODO: Replace with integration tests when real API is available
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=figma-api.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-api.spec.d.ts","sourceRoot":"","sources":["../../src/api/figma-api.spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}