@discourser/design-system 0.15.1 → 0.18.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 (281) hide show
  1. package/dist/{chunk-UNWXE6UB.cjs → chunk-2P7Z5PVP.cjs} +817 -16
  2. package/dist/chunk-2P7Z5PVP.cjs.map +1 -0
  3. package/dist/{chunk-ABC7N32K.cjs → chunk-PFWU7QSM.cjs} +464 -8
  4. package/dist/chunk-PFWU7QSM.cjs.map +1 -0
  5. package/dist/{chunk-GD6Q2FUE.js → chunk-QC7LGFM3.js} +808 -18
  6. package/dist/chunk-QC7LGFM3.js.map +1 -0
  7. package/dist/{chunk-SBKRSXSZ.js → chunk-SNUJBT5R.js} +464 -8
  8. package/dist/chunk-SNUJBT5R.js.map +1 -0
  9. package/dist/components/Accordion.figma.d.ts +2 -0
  10. package/dist/components/Accordion.figma.d.ts.map +1 -0
  11. package/dist/components/Breadcrumb.d.ts +2 -0
  12. package/dist/components/Breadcrumb.d.ts.map +1 -1
  13. package/dist/components/Breadcrumb.figma.d.ts +2 -0
  14. package/dist/components/Breadcrumb.figma.d.ts.map +1 -0
  15. package/dist/components/ContentCard/ContentCard.d.ts +13 -0
  16. package/dist/components/ContentCard/ContentCard.d.ts.map +1 -0
  17. package/dist/components/ContentCard/ContentCard.figma.d.ts +2 -0
  18. package/dist/components/ContentCard/ContentCard.figma.d.ts.map +1 -0
  19. package/dist/components/ContentCard/index.d.ts +2 -0
  20. package/dist/components/ContentCard/index.d.ts.map +1 -0
  21. package/dist/components/{Heading.d.ts → Header.d.ts} +3 -3
  22. package/dist/components/Header.d.ts.map +1 -0
  23. package/dist/components/Header.figma.d.ts +2 -0
  24. package/dist/components/Header.figma.d.ts.map +1 -0
  25. package/dist/components/Icons/AccountIcon.d.ts +6 -0
  26. package/dist/components/Icons/AccountIcon.d.ts.map +1 -0
  27. package/dist/components/Icons/AudienceIcon.d.ts +6 -0
  28. package/dist/components/Icons/AudienceIcon.d.ts.map +1 -0
  29. package/dist/components/Icons/AudienceIcon.figma.d.ts +2 -0
  30. package/dist/components/Icons/AudienceIcon.figma.d.ts.map +1 -0
  31. package/dist/components/Icons/AudioSpeakerIcon.d.ts +6 -0
  32. package/dist/components/Icons/AudioSpeakerIcon.d.ts.map +1 -0
  33. package/dist/components/Icons/AudioSpeakerIcon.figma.d.ts +2 -0
  34. package/dist/components/Icons/AudioSpeakerIcon.figma.d.ts.map +1 -0
  35. package/dist/components/Icons/BookmarkPlusIcon.d.ts +6 -0
  36. package/dist/components/Icons/BookmarkPlusIcon.d.ts.map +1 -0
  37. package/dist/components/Icons/BookmarkPlusIcon.figma.d.ts +2 -0
  38. package/dist/components/Icons/BookmarkPlusIcon.figma.d.ts.map +1 -0
  39. package/dist/components/Icons/ChevronUpIcon.d.ts +6 -0
  40. package/dist/components/Icons/ChevronUpIcon.d.ts.map +1 -0
  41. package/dist/components/Icons/ClipBoardIcon.d.ts +6 -0
  42. package/dist/components/Icons/ClipBoardIcon.d.ts.map +1 -0
  43. package/dist/components/Icons/ClipBoardIcon.figma.d.ts +2 -0
  44. package/dist/components/Icons/ClipBoardIcon.figma.d.ts.map +1 -0
  45. package/dist/components/Icons/ClockIcon.d.ts.map +1 -1
  46. package/dist/components/Icons/DashboardIcon.d.ts +6 -0
  47. package/dist/components/Icons/DashboardIcon.d.ts.map +1 -0
  48. package/dist/components/Icons/DiscourserLogo.d.ts +6 -0
  49. package/dist/components/Icons/DiscourserLogo.d.ts.map +1 -0
  50. package/dist/components/Icons/DiscourserLogo.figma.d.ts +2 -0
  51. package/dist/components/Icons/DiscourserLogo.figma.d.ts.map +1 -0
  52. package/dist/components/Icons/ExitStudioIcon.d.ts +6 -0
  53. package/dist/components/Icons/ExitStudioIcon.d.ts.map +1 -0
  54. package/dist/components/Icons/ExitStudioIcon.figma.d.ts +2 -0
  55. package/dist/components/Icons/ExitStudioIcon.figma.d.ts.map +1 -0
  56. package/dist/components/Icons/GripDotsVerticalIcon.d.ts.map +1 -1
  57. package/dist/components/Icons/HelpIcon.d.ts +6 -0
  58. package/dist/components/Icons/HelpIcon.d.ts.map +1 -0
  59. package/dist/components/Icons/MicrophoneIcon.d.ts +6 -0
  60. package/dist/components/Icons/MicrophoneIcon.d.ts.map +1 -0
  61. package/dist/components/Icons/MicrophoneIcon.figma.d.ts +2 -0
  62. package/dist/components/Icons/MicrophoneIcon.figma.d.ts.map +1 -0
  63. package/dist/components/Icons/NotebookIcon.d.ts +6 -0
  64. package/dist/components/Icons/NotebookIcon.d.ts.map +1 -0
  65. package/dist/components/Icons/NotebookPenIcon.d.ts +6 -0
  66. package/dist/components/Icons/NotebookPenIcon.d.ts.map +1 -0
  67. package/dist/components/Icons/NotebookPenIcon.figma.d.ts +2 -0
  68. package/dist/components/Icons/NotebookPenIcon.figma.d.ts.map +1 -0
  69. package/dist/components/Icons/PausePlayIcon.d.ts +6 -0
  70. package/dist/components/Icons/PausePlayIcon.d.ts.map +1 -0
  71. package/dist/components/Icons/PausePlayIcon.figma.d.ts +2 -0
  72. package/dist/components/Icons/PausePlayIcon.figma.d.ts.map +1 -0
  73. package/dist/components/Icons/PlayIcon.d.ts +6 -0
  74. package/dist/components/Icons/PlayIcon.d.ts.map +1 -0
  75. package/dist/components/Icons/PlayIcon.figma.d.ts +2 -0
  76. package/dist/components/Icons/PlayIcon.figma.d.ts.map +1 -0
  77. package/dist/components/Icons/RecordIcon.d.ts +6 -0
  78. package/dist/components/Icons/RecordIcon.d.ts.map +1 -0
  79. package/dist/components/Icons/RecordIcon.figma.d.ts +2 -0
  80. package/dist/components/Icons/RecordIcon.figma.d.ts.map +1 -0
  81. package/dist/components/Icons/RepeatQuestionIcon.d.ts +6 -0
  82. package/dist/components/Icons/RepeatQuestionIcon.d.ts.map +1 -0
  83. package/dist/components/Icons/RepeatQuestionIcon.figma.d.ts +2 -0
  84. package/dist/components/Icons/RepeatQuestionIcon.figma.d.ts.map +1 -0
  85. package/dist/components/Icons/RightArrowIcon.d.ts +6 -0
  86. package/dist/components/Icons/RightArrowIcon.d.ts.map +1 -0
  87. package/dist/components/Icons/ScenarioIcon.d.ts +6 -0
  88. package/dist/components/Icons/ScenarioIcon.d.ts.map +1 -0
  89. package/dist/components/Icons/ScrollTextIcon.d.ts +6 -0
  90. package/dist/components/Icons/ScrollTextIcon.d.ts.map +1 -0
  91. package/dist/components/Icons/ScrollTextIcon.figma.d.ts +2 -0
  92. package/dist/components/Icons/ScrollTextIcon.figma.d.ts.map +1 -0
  93. package/dist/components/Icons/SparklesIcon.d.ts +6 -0
  94. package/dist/components/Icons/SparklesIcon.d.ts.map +1 -0
  95. package/dist/components/Icons/SparklesIcon.figma.d.ts +2 -0
  96. package/dist/components/Icons/SparklesIcon.figma.d.ts.map +1 -0
  97. package/dist/components/Icons/SpeechIcon.d.ts +6 -0
  98. package/dist/components/Icons/SpeechIcon.d.ts.map +1 -0
  99. package/dist/components/Icons/SpeechIcon.figma.d.ts +2 -0
  100. package/dist/components/Icons/SpeechIcon.figma.d.ts.map +1 -0
  101. package/dist/components/Icons/StopPlayIcon.d.ts +6 -0
  102. package/dist/components/Icons/StopPlayIcon.d.ts.map +1 -0
  103. package/dist/components/Icons/StopPlayIcon.figma.d.ts +2 -0
  104. package/dist/components/Icons/StopPlayIcon.figma.d.ts.map +1 -0
  105. package/dist/components/Icons/TimerIcon.d.ts +6 -0
  106. package/dist/components/Icons/TimerIcon.d.ts.map +1 -0
  107. package/dist/components/Icons/TimerIcon.figma.d.ts +2 -0
  108. package/dist/components/Icons/TimerIcon.figma.d.ts.map +1 -0
  109. package/dist/components/Icons/UserProfileIcon.d.ts +6 -0
  110. package/dist/components/Icons/UserProfileIcon.d.ts.map +1 -0
  111. package/dist/components/Icons/UserProfileIcon.figma.d.ts +2 -0
  112. package/dist/components/Icons/UserProfileIcon.figma.d.ts.map +1 -0
  113. package/dist/components/Icons/index.d.ts +26 -1
  114. package/dist/components/Icons/index.d.ts.map +1 -1
  115. package/dist/components/NavigationMenu/NavigationMenu.d.ts +3 -0
  116. package/dist/components/NavigationMenu/NavigationMenu.d.ts.map +1 -0
  117. package/dist/components/NavigationMenu/NavigationMenu.figma.d.ts +2 -0
  118. package/dist/components/NavigationMenu/NavigationMenu.figma.d.ts.map +1 -0
  119. package/dist/components/NavigationMenu/index.d.ts +3 -0
  120. package/dist/components/NavigationMenu/index.d.ts.map +1 -0
  121. package/dist/components/NavigationMenu/types.d.ts +25 -0
  122. package/dist/components/NavigationMenu/types.d.ts.map +1 -0
  123. package/dist/components/QuickStartPage/QuickStartPage.d.ts +21 -0
  124. package/dist/components/QuickStartPage/QuickStartPage.d.ts.map +1 -0
  125. package/dist/components/QuickStartPage/index.d.ts +3 -0
  126. package/dist/components/QuickStartPage/index.d.ts.map +1 -0
  127. package/dist/components/ScenarioQueue/ScenarioQueue.figma.d.ts +2 -0
  128. package/dist/components/ScenarioQueue/ScenarioQueue.figma.d.ts.map +1 -0
  129. package/dist/components/ScenarioSettings/ScenarioSettings.d.ts +3 -0
  130. package/dist/components/ScenarioSettings/ScenarioSettings.d.ts.map +1 -0
  131. package/dist/components/ScenarioSettings/ScenarioSettings.figma.d.ts +2 -0
  132. package/dist/components/ScenarioSettings/ScenarioSettings.figma.d.ts.map +1 -0
  133. package/dist/components/ScenarioSettings/index.d.ts +3 -0
  134. package/dist/components/ScenarioSettings/index.d.ts.map +1 -0
  135. package/dist/components/ScenarioSettings/types.d.ts +54 -0
  136. package/dist/components/ScenarioSettings/types.d.ts.map +1 -0
  137. package/dist/components/index.cjs +86 -42
  138. package/dist/components/index.d.ts +14 -3
  139. package/dist/components/index.d.ts.map +1 -1
  140. package/dist/components/index.js +1 -1
  141. package/dist/figma-codex/config.d.ts +8 -0
  142. package/dist/figma-codex/config.d.ts.map +1 -0
  143. package/dist/figma-codex/fixtures/CompoundComponent/CompoundComponent.d.ts +6 -0
  144. package/dist/figma-codex/fixtures/CompoundComponent/CompoundComponent.d.ts.map +1 -0
  145. package/dist/figma-codex/fixtures/CompoundComponent/index.d.ts +2 -0
  146. package/dist/figma-codex/fixtures/CompoundComponent/index.d.ts.map +1 -0
  147. package/dist/figma-codex/fixtures/CompoundComponent.figma.d.ts +2 -0
  148. package/dist/figma-codex/fixtures/CompoundComponent.figma.d.ts.map +1 -0
  149. package/dist/figma-codex/fixtures/SimpleComponent.d.ts +8 -0
  150. package/dist/figma-codex/fixtures/SimpleComponent.d.ts.map +1 -0
  151. package/dist/figma-codex/fixtures/SimpleComponent.figma.d.ts +2 -0
  152. package/dist/figma-codex/fixtures/SimpleComponent.figma.d.ts.map +1 -0
  153. package/dist/figma-codex/generate.d.ts +6 -0
  154. package/dist/figma-codex/generate.d.ts.map +1 -0
  155. package/dist/figma-codex/parser.d.ts +18 -0
  156. package/dist/figma-codex/parser.d.ts.map +1 -0
  157. package/dist/figma-codex/resolver.d.ts +5 -0
  158. package/dist/figma-codex/resolver.d.ts.map +1 -0
  159. package/dist/figma-codex/schema.d.ts +60 -0
  160. package/dist/figma-codex/schema.d.ts.map +1 -0
  161. package/dist/figma-codex/writer.d.ts +8 -0
  162. package/dist/figma-codex/writer.d.ts.map +1 -0
  163. package/dist/figma-codex.json +679 -0
  164. package/dist/index.cjs +90 -46
  165. package/dist/index.js +2 -2
  166. package/dist/preset/index.cjs +2 -2
  167. package/dist/preset/index.d.ts.map +1 -1
  168. package/dist/preset/index.js +1 -1
  169. package/dist/preset/recipes/accordion.d.ts.map +1 -1
  170. package/dist/preset/recipes/breadcrumb.d.ts.map +1 -1
  171. package/dist/preset/recipes/content-card.d.ts +2 -0
  172. package/dist/preset/recipes/content-card.d.ts.map +1 -0
  173. package/dist/preset/recipes/index.d.ts +4 -0
  174. package/dist/preset/recipes/index.d.ts.map +1 -1
  175. package/dist/preset/recipes/navigation-menu.d.ts +2 -0
  176. package/dist/preset/recipes/navigation-menu.d.ts.map +1 -0
  177. package/dist/preset/recipes/scenario-settings.d.ts +2 -0
  178. package/dist/preset/recipes/scenario-settings.d.ts.map +1 -0
  179. package/package.json +26 -2
  180. package/src/components/Accordion.figma.tsx +20 -0
  181. package/src/components/Breadcrumb.figma.tsx +18 -0
  182. package/src/components/Breadcrumb.tsx +33 -15
  183. package/src/components/ContentCard/ContentCard.figma.tsx +21 -0
  184. package/src/components/ContentCard/ContentCard.test.tsx +197 -0
  185. package/src/components/ContentCard/ContentCard.tsx +19 -0
  186. package/src/components/ContentCard/index.ts +13 -0
  187. package/src/components/Header.figma.tsx +25 -0
  188. package/src/components/{Heading.tsx → Header.tsx} +2 -2
  189. package/src/components/Icons/AccountIcon.tsx +26 -0
  190. package/src/components/Icons/AudienceIcon.figma.tsx +10 -0
  191. package/src/components/Icons/AudienceIcon.tsx +20 -0
  192. package/src/components/Icons/AudioSpeakerIcon.figma.tsx +10 -0
  193. package/src/components/Icons/AudioSpeakerIcon.tsx +26 -0
  194. package/src/components/Icons/BookmarkPlusIcon.figma.tsx +10 -0
  195. package/src/components/Icons/BookmarkPlusIcon.tsx +26 -0
  196. package/src/components/Icons/ChevronUpIcon.tsx +24 -0
  197. package/src/components/Icons/ClipBoardIcon.figma.tsx +10 -0
  198. package/src/components/Icons/ClipBoardIcon.tsx +61 -0
  199. package/src/components/Icons/ClockIcon.tsx +6 -6
  200. package/src/components/Icons/DashboardIcon.tsx +47 -0
  201. package/src/components/Icons/Discourser-Logo.svg +14 -0
  202. package/src/components/Icons/DiscourserLogo.figma.tsx +10 -0
  203. package/src/components/Icons/DiscourserLogo.tsx +72 -0
  204. package/src/components/Icons/ExitStudioIcon.figma.tsx +10 -0
  205. package/src/components/Icons/ExitStudioIcon.tsx +34 -0
  206. package/src/components/Icons/GripDotsVerticalIcon.tsx +6 -6
  207. package/src/components/Icons/HelpIcon.tsx +26 -0
  208. package/src/components/Icons/MicrophoneIcon.figma.tsx +10 -0
  209. package/src/components/Icons/MicrophoneIcon.tsx +40 -0
  210. package/src/components/Icons/NotebookIcon.tsx +26 -0
  211. package/src/components/Icons/NotebookPenIcon.figma.tsx +10 -0
  212. package/src/components/Icons/NotebookPenIcon.tsx +26 -0
  213. package/src/components/Icons/PausePlayIcon.figma.tsx +10 -0
  214. package/src/components/Icons/PausePlayIcon.tsx +41 -0
  215. package/src/components/Icons/PlayIcon.figma.tsx +10 -0
  216. package/src/components/Icons/PlayIcon.tsx +33 -0
  217. package/src/components/Icons/RecordIcon.figma.tsx +10 -0
  218. package/src/components/Icons/RecordIcon.tsx +41 -0
  219. package/src/components/Icons/RepeatQuestionIcon.figma.tsx +10 -0
  220. package/src/components/Icons/RepeatQuestionIcon.tsx +26 -0
  221. package/src/components/Icons/RightArrowIcon.tsx +23 -0
  222. package/src/components/Icons/ScenarioIcon.tsx +26 -0
  223. package/src/components/Icons/ScrollTextIcon.figma.tsx +10 -0
  224. package/src/components/Icons/ScrollTextIcon.tsx +26 -0
  225. package/src/components/Icons/SparklesIcon.figma.tsx +10 -0
  226. package/src/components/Icons/SparklesIcon.tsx +26 -0
  227. package/src/components/Icons/SpeechIcon.figma.tsx +10 -0
  228. package/src/components/Icons/SpeechIcon.tsx +26 -0
  229. package/src/components/Icons/StopPlayIcon.figma.tsx +10 -0
  230. package/src/components/Icons/StopPlayIcon.tsx +35 -0
  231. package/src/components/Icons/TimerIcon.figma.tsx +10 -0
  232. package/src/components/Icons/TimerIcon.tsx +26 -0
  233. package/src/components/Icons/UserProfileIcon.figma.tsx +10 -0
  234. package/src/components/Icons/UserProfileIcon.tsx +26 -0
  235. package/src/components/Icons/index.ts +39 -2
  236. package/src/components/NavigationMenu/NavigationMenu.figma.tsx +26 -0
  237. package/src/components/NavigationMenu/NavigationMenu.test.tsx +524 -0
  238. package/src/components/NavigationMenu/NavigationMenu.tsx +102 -0
  239. package/src/components/NavigationMenu/index.ts +2 -0
  240. package/src/components/NavigationMenu/types.ts +27 -0
  241. package/src/components/QuickStartPage/QuickStartPage.tsx +627 -0
  242. package/src/components/QuickStartPage/index.ts +2 -0
  243. package/src/components/ScenarioQueue/ScenarioQueue.figma.tsx +37 -0
  244. package/src/components/ScenarioSettings/ScenarioSettings.figma.tsx +12 -0
  245. package/src/components/ScenarioSettings/ScenarioSettings.test.tsx +406 -0
  246. package/src/components/ScenarioSettings/ScenarioSettings.tsx +386 -0
  247. package/src/components/ScenarioSettings/index.ts +11 -0
  248. package/src/components/ScenarioSettings/types.ts +70 -0
  249. package/src/components/__tests__/Breadcrumb.test.tsx +94 -0
  250. package/src/components/index.ts +38 -4
  251. package/src/figma-codex/README.md +186 -0
  252. package/src/figma-codex/__tests__/config.test.ts +63 -0
  253. package/src/figma-codex/__tests__/generate.test.ts +78 -0
  254. package/src/figma-codex/__tests__/parser.test.ts +138 -0
  255. package/src/figma-codex/__tests__/resolver.test.ts +196 -0
  256. package/src/figma-codex/__tests__/writer.test.ts +111 -0
  257. package/src/figma-codex/config.ts +42 -0
  258. package/src/figma-codex/fixtures/CompoundComponent/CompoundComponent.tsx +17 -0
  259. package/src/figma-codex/fixtures/CompoundComponent/index.ts +1 -0
  260. package/src/figma-codex/fixtures/CompoundComponent.figma.tsx +14 -0
  261. package/src/figma-codex/fixtures/SimpleComponent.figma.tsx +10 -0
  262. package/src/figma-codex/fixtures/SimpleComponent.tsx +10 -0
  263. package/src/figma-codex/fixtures/expected-output.json +78 -0
  264. package/src/figma-codex/generate.ts +106 -0
  265. package/src/figma-codex/parser.ts +138 -0
  266. package/src/figma-codex/resolver.ts +280 -0
  267. package/src/figma-codex/schema.ts +79 -0
  268. package/src/figma-codex/writer.ts +54 -0
  269. package/src/preset/index.ts +6 -0
  270. package/src/preset/recipes/accordion.ts +8 -5
  271. package/src/preset/recipes/breadcrumb.ts +34 -2
  272. package/src/preset/recipes/content-card.ts +124 -0
  273. package/src/preset/recipes/index.ts +4 -0
  274. package/src/preset/recipes/navigation-menu.ts +97 -0
  275. package/src/preset/recipes/scenario-settings.ts +182 -0
  276. package/src/test/setup.ts +12 -9
  277. package/dist/chunk-ABC7N32K.cjs.map +0 -1
  278. package/dist/chunk-GD6Q2FUE.js.map +0 -1
  279. package/dist/chunk-SBKRSXSZ.js.map +0 -1
  280. package/dist/chunk-UNWXE6UB.cjs.map +0 -1
  281. package/dist/components/Heading.d.ts.map +0 -1
@@ -0,0 +1,111 @@
1
+ // @vitest-environment node
2
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
3
+ import { mkdirSync, rmSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { writeManifest } from '../writer';
7
+ import type { ComponentEntry } from '../schema';
8
+
9
+ const sampleEntry: ComponentEntry = {
10
+ name: 'Button',
11
+ type: 'simple',
12
+ figma: {
13
+ fileKey: 'ABC123',
14
+ nodeId: '1:2',
15
+ url: 'https://www.figma.com/design/ABC123/F?node-id=1-2',
16
+ },
17
+ imports: {
18
+ primary: "import { Button } from '@discourser/design-system/Button'",
19
+ namedExports: ['Button'],
20
+ subpath: '@discourser/design-system/Button',
21
+ },
22
+ props: [{ name: 'label', type: 'string', required: true }],
23
+ example: '<Button label="Click me" />',
24
+ sourcePath: 'src/components/Button.tsx',
25
+ };
26
+
27
+ describe('writeManifest', () => {
28
+ let tmpDir: string;
29
+ let outputPath: string;
30
+
31
+ beforeEach(() => {
32
+ tmpDir = join(tmpdir(), `figma-codex-writer-${Date.now()}`);
33
+ mkdirSync(tmpDir, { recursive: true });
34
+ outputPath = join(tmpDir, 'figma-codex.json');
35
+ });
36
+
37
+ afterEach(() => {
38
+ rmSync(tmpDir, { recursive: true, force: true });
39
+ });
40
+
41
+ it('writes valid JSON to the output path', () => {
42
+ writeManifest(
43
+ { Button: sampleEntry },
44
+ { packageName: '@discourser/design-system', outputPath },
45
+ tmpDir,
46
+ );
47
+ const raw = readFileSync(outputPath, 'utf-8');
48
+ expect(() => JSON.parse(raw)).not.toThrow();
49
+ });
50
+
51
+ it('includes schema version 1.0.0', () => {
52
+ writeManifest(
53
+ { Button: sampleEntry },
54
+ { packageName: '@discourser/design-system', outputPath },
55
+ tmpDir,
56
+ );
57
+ const manifest = JSON.parse(readFileSync(outputPath, 'utf-8'));
58
+ expect(manifest.version).toBe('1.0.0');
59
+ });
60
+
61
+ it('includes generatedAt ISO timestamp', () => {
62
+ writeManifest(
63
+ { Button: sampleEntry },
64
+ { packageName: '@discourser/design-system', outputPath },
65
+ tmpDir,
66
+ );
67
+ const manifest = JSON.parse(readFileSync(outputPath, 'utf-8'));
68
+ expect(manifest.generatedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
69
+ });
70
+
71
+ it('includes all provided components', () => {
72
+ writeManifest(
73
+ { Button: sampleEntry },
74
+ { packageName: '@discourser/design-system', outputPath },
75
+ tmpDir,
76
+ );
77
+ const manifest = JSON.parse(readFileSync(outputPath, 'utf-8'));
78
+ expect(manifest.components).toHaveProperty('Button');
79
+ expect(manifest.components.Button.name).toBe('Button');
80
+ });
81
+
82
+ it('uses 2-space indentation', () => {
83
+ writeManifest(
84
+ { Button: sampleEntry },
85
+ { packageName: '@discourser/design-system', outputPath },
86
+ tmpDir,
87
+ );
88
+ const raw = readFileSync(outputPath, 'utf-8');
89
+ expect(raw).toContain(' "version"');
90
+ });
91
+
92
+ it('collects figmaFiles from all components', () => {
93
+ writeManifest(
94
+ { Button: sampleEntry },
95
+ { packageName: '@discourser/design-system', outputPath },
96
+ tmpDir,
97
+ );
98
+ const manifest = JSON.parse(readFileSync(outputPath, 'utf-8'));
99
+ expect(manifest.figmaFiles).toHaveProperty('ABC123');
100
+ });
101
+
102
+ it('handles empty component list without throwing', () => {
103
+ expect(() =>
104
+ writeManifest(
105
+ {},
106
+ { packageName: '@discourser/design-system', outputPath },
107
+ tmpDir,
108
+ ),
109
+ ).not.toThrow();
110
+ });
111
+ });
@@ -0,0 +1,42 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ export interface FigmaCodexConfig {
5
+ include: string[];
6
+ outputPath: string;
7
+ packageName: string;
8
+ tsconfig: string;
9
+ }
10
+
11
+ const DEFAULTS: FigmaCodexConfig = {
12
+ include: ['src/components/**/*.figma.tsx'],
13
+ outputPath: 'dist/figma-codex.json',
14
+ packageName: '',
15
+ tsconfig: 'tsconfig.json',
16
+ };
17
+
18
+ export function loadConfig(
19
+ projectRoot: string = process.cwd(),
20
+ ): FigmaCodexConfig {
21
+ let userConfig: Partial<FigmaCodexConfig> = {};
22
+
23
+ const configPath = join(projectRoot, 'figma-codex.config.json');
24
+ if (existsSync(configPath)) {
25
+ userConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
26
+ }
27
+
28
+ let packageName = userConfig.packageName ?? '';
29
+ if (!packageName) {
30
+ const pkgPath = join(projectRoot, 'package.json');
31
+ if (existsSync(pkgPath)) {
32
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
33
+ packageName = pkg.name ?? '';
34
+ }
35
+ }
36
+
37
+ return {
38
+ ...DEFAULTS,
39
+ ...userConfig,
40
+ packageName,
41
+ };
42
+ }
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+ // Test fixture — resolver uses text-based regex, imports not required to be valid.
3
+ // Globals declared below satisfy TypeScript; the resolver reads this file as raw text.
4
+ import type { FC, ComponentProps } from 'react';
5
+
6
+ declare const ark: {
7
+ div: FC<ComponentProps<'div'>>;
8
+ h2: FC<ComponentProps<'h2'>>;
9
+ span: FC<ComponentProps<'span'>>;
10
+ };
11
+ declare function withProvider<T>(el: T, slot: string): T;
12
+ declare function withContext<T>(el: T, slot: string): T;
13
+
14
+ export type RootProps = ComponentProps<'div'>;
15
+ export const Root = withProvider(ark.div, 'root');
16
+ export const Header = withContext(ark.div, 'header');
17
+ export const Body = withContext(ark.div, 'body');
@@ -0,0 +1 @@
1
+ export { Root, Header, Body, type RootProps } from './CompoundComponent';
@@ -0,0 +1,14 @@
1
+ import figma from '@figma/code-connect';
2
+ import * as CompoundComponent from './CompoundComponent/index';
3
+
4
+ figma.connect(
5
+ CompoundComponent.Root,
6
+ 'https://www.figma.com/design/ABC123/TestFile?node-id=5-10',
7
+ {
8
+ example: () => (
9
+ <CompoundComponent.Root>
10
+ <CompoundComponent.Header>Title</CompoundComponent.Header>
11
+ </CompoundComponent.Root>
12
+ ),
13
+ },
14
+ );
@@ -0,0 +1,10 @@
1
+ import figma from '@figma/code-connect';
2
+ import { SimpleComponent } from './SimpleComponent';
3
+
4
+ figma.connect(
5
+ SimpleComponent,
6
+ 'https://www.figma.com/design/ABC123/TestFile?node-id=1-2',
7
+ {
8
+ example: () => <SimpleComponent label="Hello" />,
9
+ },
10
+ );
@@ -0,0 +1,10 @@
1
+ export interface SimpleComponentProps {
2
+ /** The display label */
3
+ label: string;
4
+ /** Optional size variant */
5
+ size?: 'sm' | 'md' | 'lg';
6
+ }
7
+
8
+ export const SimpleComponent = (props: SimpleComponentProps) => (
9
+ <div>{props.label}</div>
10
+ );
@@ -0,0 +1,78 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "packageName": "@test/design-system",
4
+ "generatedAt": "2026-02-27T19:21:46.956Z",
5
+ "gitHash": "3fd0a961",
6
+ "figmaFiles": {
7
+ "ABC123": {
8
+ "fileKey": "ABC123"
9
+ }
10
+ },
11
+ "components": {
12
+ "CompoundComponent": {
13
+ "name": "CompoundComponent",
14
+ "type": "compound",
15
+ "figma": {
16
+ "fileKey": "ABC123",
17
+ "nodeId": "5:10",
18
+ "url": "https://www.figma.com/design/ABC123/TestFile?node-id=5-10"
19
+ },
20
+ "imports": {
21
+ "primary": "import * as CompoundComponent from '@test/design-system/CompoundComponent'",
22
+ "namedExports": [
23
+ "CompoundComponent.Root",
24
+ "CompoundComponent.Header",
25
+ "CompoundComponent.Body"
26
+ ]
27
+ },
28
+ "props": [],
29
+ "subComponents": [
30
+ {
31
+ "name": "Root",
32
+ "element": "div"
33
+ },
34
+ {
35
+ "name": "Header",
36
+ "element": "div"
37
+ },
38
+ {
39
+ "name": "Body",
40
+ "element": "div"
41
+ }
42
+ ],
43
+ "example": "<CompoundComponent.Root>\n <CompoundComponent.Header>Title</CompoundComponent.Header>\n </CompoundComponent.Root>",
44
+ "sourcePath": "src/figma-codex/fixtures/CompoundComponent/index.ts"
45
+ },
46
+ "SimpleComponent": {
47
+ "name": "SimpleComponent",
48
+ "type": "simple",
49
+ "figma": {
50
+ "fileKey": "ABC123",
51
+ "nodeId": "1:2",
52
+ "url": "https://www.figma.com/design/ABC123/TestFile?node-id=1-2"
53
+ },
54
+ "imports": {
55
+ "primary": "import { SimpleComponent } from '@test/design-system/SimpleComponent'",
56
+ "namedExports": [
57
+ "SimpleComponent"
58
+ ]
59
+ },
60
+ "props": [
61
+ {
62
+ "name": "label",
63
+ "type": "string",
64
+ "required": true,
65
+ "description": "The display label"
66
+ },
67
+ {
68
+ "name": "size",
69
+ "type": "'sm' | 'md' | 'lg'",
70
+ "required": false,
71
+ "description": "Optional size variant"
72
+ }
73
+ ],
74
+ "example": "<SimpleComponent label=\"Hello\" />",
75
+ "sourcePath": "src/figma-codex/fixtures/SimpleComponent.tsx"
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,106 @@
1
+ import { readdirSync, readFileSync } from 'node:fs';
2
+ import { join, resolve, isAbsolute } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { parseFigmaFile, ParseError } from './parser';
5
+ import { resolveComponent } from './resolver';
6
+ import { writeManifest } from './writer';
7
+ import { loadConfig, type FigmaCodexConfig } from './config';
8
+ import type { ComponentEntry } from './schema';
9
+
10
+ /** Recursively find all .figma.tsx files under a directory */
11
+ function findFigmaFiles(rootDir: string): string[] {
12
+ const files: string[] = [];
13
+
14
+ function walk(dir: string) {
15
+ let entries;
16
+ try {
17
+ entries = readdirSync(dir, { withFileTypes: true });
18
+ } catch {
19
+ return;
20
+ }
21
+ for (const entry of entries) {
22
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
23
+ const full = join(dir, entry.name);
24
+ if (entry.isDirectory()) walk(full);
25
+ else if (entry.name.endsWith('.figma.tsx')) files.push(full);
26
+ }
27
+ }
28
+
29
+ walk(rootDir);
30
+ return files;
31
+ }
32
+
33
+ export interface GenerateOptions extends FigmaCodexConfig {
34
+ projectRoot?: string;
35
+ }
36
+
37
+ export function generate(options: GenerateOptions): void {
38
+ const projectRoot = options.projectRoot ?? process.cwd();
39
+
40
+ // Resolve include patterns to base directories then scan recursively
41
+ const figmaFiles: string[] = [];
42
+ for (const pattern of options.include) {
43
+ // Extract the literal prefix before the first '*' as the base directory to walk.
44
+ // Patterns starting with '*' (e.g. '**/*.figma.tsx') fall back to projectRoot.
45
+ // Patterns like 'src/components/**/*.figma.tsx' correctly resolve to 'src/components/'.
46
+ const starIdx = pattern.indexOf('*');
47
+ const baseDir =
48
+ starIdx > 0
49
+ ? isAbsolute(pattern.slice(0, starIdx))
50
+ ? pattern.slice(0, starIdx).replace(/\/$/, '')
51
+ : join(projectRoot, pattern.slice(0, starIdx).replace(/\/$/, ''))
52
+ : projectRoot;
53
+ const found = findFigmaFiles(baseDir);
54
+ figmaFiles.push(...found);
55
+ }
56
+
57
+ if (figmaFiles.length === 0) {
58
+ console.warn(
59
+ '[figma-codex] No .figma.tsx files found. Check your include patterns.',
60
+ );
61
+ return;
62
+ }
63
+
64
+ const components: Record<string, ComponentEntry> = {};
65
+
66
+ for (const filePath of figmaFiles) {
67
+ try {
68
+ const content = readFileSync(filePath, 'utf-8');
69
+ const parsed = parseFigmaFile(content, filePath);
70
+ const entry = resolveComponent(parsed, options);
71
+ components[entry.name] = entry;
72
+ console.log(`[figma-codex] ✓ ${entry.name} (${entry.type})`);
73
+ } catch (err) {
74
+ if (err instanceof ParseError) {
75
+ console.warn(`[figma-codex] ⚠ Skipping ${filePath}: ${err.message}`);
76
+ } else {
77
+ console.warn(`[figma-codex] ⚠ Skipping ${filePath}: ${String(err)}`);
78
+ }
79
+ }
80
+ }
81
+
82
+ if (Object.keys(components).length === 0) {
83
+ throw new Error('[figma-codex] No components successfully parsed.');
84
+ }
85
+
86
+ writeManifest(
87
+ components,
88
+ { packageName: options.packageName, outputPath: options.outputPath },
89
+ projectRoot,
90
+ );
91
+ console.log(
92
+ `[figma-codex] Generated ${Object.keys(components).length} components → ${options.outputPath}`,
93
+ );
94
+ }
95
+
96
+ // CLI entry point
97
+ const currentFile = fileURLToPath(import.meta.url);
98
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(currentFile)) {
99
+ try {
100
+ const config = loadConfig();
101
+ generate({ ...config, projectRoot: process.cwd() });
102
+ } catch (err) {
103
+ console.error(String(err));
104
+ process.exit(1);
105
+ }
106
+ }
@@ -0,0 +1,138 @@
1
+ export class ParseError extends Error {
2
+ constructor(
3
+ message: string,
4
+ public readonly filePath: string,
5
+ ) {
6
+ super(`ParseError: ${message} (${filePath})`);
7
+ this.name = 'ParseError';
8
+ }
9
+ }
10
+
11
+ export interface ParsedFigmaFile {
12
+ filePath: string;
13
+ importStyle: 'named' | 'namespace';
14
+ componentName: string;
15
+ importSource: string;
16
+ connectSubComponent?: string;
17
+ figmaUrl: string;
18
+ figmaFileKey: string;
19
+ figmaNodeId: string;
20
+ example: string;
21
+ propsMapping: Record<string, string>;
22
+ }
23
+
24
+ function parseNodeId(url: string): string {
25
+ const match = url.match(/node-id=([^&'"]+)/);
26
+ if (!match) return '';
27
+ return match[1].replace(/-/g, ':');
28
+ }
29
+
30
+ function parseFileKey(url: string): string {
31
+ const match = url.match(/figma\.com\/design\/([^/?]+)/);
32
+ return match?.[1] ?? '';
33
+ }
34
+
35
+ function extractExample(content: string): string {
36
+ const exampleIdx = content.indexOf('example:');
37
+ if (exampleIdx === -1) return '';
38
+
39
+ const arrowIdx = content.indexOf('=>', exampleIdx);
40
+ if (arrowIdx === -1) return '';
41
+
42
+ const afterArrow = content.slice(arrowIdx + 2).trimStart();
43
+
44
+ if (afterArrow.startsWith('(')) {
45
+ // Multi-line: track balanced parens
46
+ let depth = 0;
47
+ let i = 0;
48
+ for (; i < afterArrow.length; i++) {
49
+ if (afterArrow[i] === '(') depth++;
50
+ else if (afterArrow[i] === ')') {
51
+ depth--;
52
+ if (depth === 0) break;
53
+ }
54
+ }
55
+ const inner = afterArrow.slice(1, i).trim();
56
+ return inner;
57
+ } else {
58
+ // Single-line: grab to end of line, strip trailing comma
59
+ const lineEnd = afterArrow.indexOf('\n');
60
+ const line = lineEnd === -1 ? afterArrow : afterArrow.slice(0, lineEnd);
61
+ return line.replace(/,\s*$/, '').trim();
62
+ }
63
+ }
64
+
65
+ function extractPropsMapping(content: string): Record<string, string> {
66
+ const result: Record<string, string> = {};
67
+ const propsBlockMatch = content.match(/props:\s*\{([^}]+)\}/);
68
+ if (!propsBlockMatch) return result;
69
+
70
+ const block = propsBlockMatch[1];
71
+ const propNames = block.match(/(\w+)\s*:/g);
72
+ if (propNames) {
73
+ for (const p of propNames) {
74
+ result[p.replace(':', '').trim()] = 'figma.enum';
75
+ }
76
+ }
77
+ return result;
78
+ }
79
+
80
+ export function parseFigmaFile(
81
+ content: string,
82
+ filePath: string,
83
+ ): ParsedFigmaFile {
84
+ // Try namespace import: import * as X from './path'
85
+ const nsMatch = content.match(
86
+ /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/,
87
+ );
88
+ // Try named import: import { X } from './path' (skip figma import itself)
89
+ const namedMatches = [
90
+ ...content.matchAll(/import\s+\{\s*(\w+)\s*\}\s+from\s+['"]([^'"]+)['"]/g),
91
+ ];
92
+ // Filter out the figma import itself
93
+ const namedMatch = namedMatches.find(
94
+ (m) => !m[2].includes('figma') && !m[2].includes('@figma'),
95
+ );
96
+
97
+ if (!nsMatch && !namedMatch) {
98
+ throw new ParseError('No component import found', filePath);
99
+ }
100
+
101
+ const connectMatch = content.match(
102
+ /figma\.connect\(\s*(\w+)(?:\.(\w+))?\s*,\s*['"]([^'"]+)['"]/,
103
+ );
104
+ if (!connectMatch) {
105
+ throw new ParseError('No figma.connect() call found', filePath);
106
+ }
107
+
108
+ const figmaUrl = connectMatch[3];
109
+ const connectSubComponent = connectMatch[2];
110
+
111
+ if (nsMatch) {
112
+ return {
113
+ filePath,
114
+ importStyle: 'namespace',
115
+ componentName: nsMatch[1],
116
+ importSource: nsMatch[2],
117
+ connectSubComponent,
118
+ figmaUrl,
119
+ figmaFileKey: parseFileKey(figmaUrl),
120
+ figmaNodeId: parseNodeId(figmaUrl),
121
+ example: extractExample(content),
122
+ propsMapping: extractPropsMapping(content),
123
+ };
124
+ }
125
+
126
+ return {
127
+ filePath,
128
+ importStyle: 'named',
129
+ componentName: namedMatch![1],
130
+ importSource: namedMatch![2],
131
+ connectSubComponent,
132
+ figmaUrl,
133
+ figmaFileKey: parseFileKey(figmaUrl),
134
+ figmaNodeId: parseNodeId(figmaUrl),
135
+ example: extractExample(content),
136
+ propsMapping: extractPropsMapping(content),
137
+ };
138
+ }