@fragments-sdk/cli 0.2.2

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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. package/src/viewer/vite-plugin.ts +2143 -0
@@ -0,0 +1,337 @@
1
+ import { resolve, dirname, basename } from 'node:path';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { existsSync } from 'node:fs';
4
+ import fg from 'fast-glob';
5
+ import type { SegmentsConfig } from './types.js';
6
+ import { BRAND } from './constants.js';
7
+
8
+ export interface DiscoveredFile {
9
+ /** Absolute path to the file */
10
+ absolutePath: string;
11
+ /** Path relative to config directory */
12
+ relativePath: string;
13
+ }
14
+
15
+ /**
16
+ * Discovered component with source file information
17
+ */
18
+ export interface DiscoveredComponent {
19
+ /** Component name (e.g., "Button") */
20
+ name: string;
21
+ /** Absolute path to the component source file */
22
+ sourcePath: string;
23
+ /** Path relative to config directory */
24
+ relativePath: string;
25
+ /** Path to storybook file if found */
26
+ storyPath?: string;
27
+ }
28
+
29
+ /**
30
+ * Discover recipe files (*.recipe.ts) under the config directory
31
+ */
32
+ export async function discoverRecipeFiles(
33
+ configDir: string,
34
+ exclude?: string[]
35
+ ): Promise<DiscoveredFile[]> {
36
+ const pattern = `**/*${BRAND.recipeFileExtension}`;
37
+ const files = await fg(pattern, {
38
+ cwd: configDir,
39
+ ignore: exclude ?? ['**/node_modules/**', '**/dist/**'],
40
+ absolute: false,
41
+ });
42
+
43
+ return files.map((relativePath) => ({
44
+ relativePath,
45
+ absolutePath: resolve(configDir, relativePath),
46
+ }));
47
+ }
48
+
49
+ /**
50
+ * Discover segment files matching the config patterns
51
+ */
52
+ export async function discoverSegmentFiles(
53
+ config: SegmentsConfig,
54
+ configDir: string
55
+ ): Promise<DiscoveredFile[]> {
56
+ const files = await fg(config.include, {
57
+ cwd: configDir,
58
+ ignore: config.exclude ?? [],
59
+ absolute: false,
60
+ });
61
+
62
+ return files.map((relativePath) => ({
63
+ relativePath,
64
+ absolutePath: resolve(configDir, relativePath),
65
+ }));
66
+ }
67
+
68
+ /**
69
+ * Discover component files for coverage validation
70
+ */
71
+ export async function discoverComponentFiles(
72
+ config: SegmentsConfig,
73
+ configDir: string
74
+ ): Promise<DiscoveredFile[]> {
75
+ if (!config.components || config.components.length === 0) {
76
+ return [];
77
+ }
78
+
79
+ const files = await fg(config.components, {
80
+ cwd: configDir,
81
+ ignore: [
82
+ ...(config.exclude ?? []),
83
+ // Exclude segment files themselves
84
+ ...config.include,
85
+ // Exclude test files
86
+ '**/*.test.*',
87
+ '**/*.spec.*',
88
+ '**/__tests__/**',
89
+ ],
90
+ absolute: false,
91
+ });
92
+
93
+ return files.map((relativePath) => ({
94
+ relativePath,
95
+ absolutePath: resolve(configDir, relativePath),
96
+ }));
97
+ }
98
+
99
+ /**
100
+ * Extract component name from file path
101
+ */
102
+ export function extractComponentName(filePath: string): string {
103
+ // Handle index.tsx files - use parent directory name
104
+ const parts = filePath.replace(/\\/g, '/').split('/');
105
+ const fileName = parts[parts.length - 1];
106
+
107
+ if (fileName === 'index.tsx' || fileName === 'index.ts') {
108
+ return parts[parts.length - 2] ?? 'Unknown';
109
+ }
110
+
111
+ // Remove extension
112
+ return fileName.replace(/\.(tsx?|jsx?)$/, '');
113
+ }
114
+
115
+ /**
116
+ * Default patterns for component discovery
117
+ */
118
+ const DEFAULT_COMPONENT_PATTERNS = [
119
+ 'src/components/**/*.tsx',
120
+ 'src/components/**/index.tsx',
121
+ 'components/**/*.tsx',
122
+ 'lib/components/**/*.tsx',
123
+ 'packages/*/src/components/**/*.tsx',
124
+ ];
125
+
126
+ /**
127
+ * Patterns to exclude from component discovery
128
+ */
129
+ const DEFAULT_EXCLUDE_PATTERNS = [
130
+ '**/*.test.*',
131
+ '**/*.spec.*',
132
+ '**/*.stories.*',
133
+ '**/*.story.*',
134
+ '**/__tests__/**',
135
+ '**/__mocks__/**',
136
+ '**/node_modules/**',
137
+ '**/dist/**',
138
+ ];
139
+
140
+ /**
141
+ * Discover components from source files
142
+ *
143
+ * This function finds React components by:
144
+ * 1. Looking for TypeScript/TSX files in common component directories
145
+ * 2. Filtering out test files, stories, and internal files
146
+ * 3. Extracting component names from file names or directories
147
+ */
148
+ export async function discoverComponentsFromSource(
149
+ configDir: string,
150
+ patterns?: string[],
151
+ exclude?: string[]
152
+ ): Promise<DiscoveredComponent[]> {
153
+ const searchPatterns = patterns && patterns.length > 0
154
+ ? patterns
155
+ : DEFAULT_COMPONENT_PATTERNS;
156
+
157
+ const excludePatterns = [
158
+ ...DEFAULT_EXCLUDE_PATTERNS,
159
+ ...(exclude ?? []),
160
+ ];
161
+
162
+ const files = await fg(searchPatterns, {
163
+ cwd: configDir,
164
+ ignore: excludePatterns,
165
+ absolute: false,
166
+ });
167
+
168
+ // Filter to only component-like files (start with uppercase)
169
+ const componentFiles = files.filter((file) => {
170
+ const name = extractComponentName(file);
171
+ return /^[A-Z]/.test(name);
172
+ });
173
+
174
+ // Find associated story files
175
+ const storyPatterns = [
176
+ '**/*.stories.tsx',
177
+ '**/*.stories.ts',
178
+ '**/*.story.tsx',
179
+ '**/*.story.ts',
180
+ ];
181
+
182
+ const storyFiles = await fg(storyPatterns, {
183
+ cwd: configDir,
184
+ ignore: ['**/node_modules/**', '**/dist/**'],
185
+ absolute: false,
186
+ });
187
+
188
+ const storyMap = new Map<string, string>();
189
+ for (const storyFile of storyFiles) {
190
+ const name = extractComponentName(storyFile.replace(/\.stories?\.(tsx?|jsx?)$/, '.tsx'));
191
+ storyMap.set(name, storyFile);
192
+ }
193
+
194
+ // Build discovered components
195
+ const components: DiscoveredComponent[] = [];
196
+
197
+ for (const file of componentFiles) {
198
+ const name = extractComponentName(file);
199
+ const absolutePath = resolve(configDir, file);
200
+
201
+ // Look for story file
202
+ const storyFile = storyMap.get(name);
203
+
204
+ components.push({
205
+ name,
206
+ sourcePath: absolutePath,
207
+ relativePath: file,
208
+ storyPath: storyFile ? resolve(configDir, storyFile) : undefined,
209
+ });
210
+ }
211
+
212
+ // Sort by name
213
+ components.sort((a, b) => a.name.localeCompare(b.name));
214
+
215
+ return components;
216
+ }
217
+
218
+ /**
219
+ * Discover components from a barrel export file (index.ts)
220
+ *
221
+ * Parses the barrel file to find exported components.
222
+ * This is useful for libraries that expose components through a single entry point.
223
+ */
224
+ export async function discoverComponentsFromBarrel(
225
+ barrelPath: string,
226
+ configDir: string
227
+ ): Promise<DiscoveredComponent[]> {
228
+ const absoluteBarrelPath = resolve(configDir, barrelPath);
229
+
230
+ if (!existsSync(absoluteBarrelPath)) {
231
+ return [];
232
+ }
233
+
234
+ const content = await readFile(absoluteBarrelPath, 'utf-8');
235
+ const components: DiscoveredComponent[] = [];
236
+
237
+ // Match export statements like:
238
+ // export { Button } from './Button'
239
+ // export { Card, CardHeader } from './Card'
240
+ // export * from './Modal'
241
+ const exportRegex = /export\s+(?:\*|{([^}]+)})\s+from\s+['"]([^'"]+)['"]/g;
242
+
243
+ let match;
244
+ while ((match = exportRegex.exec(content)) !== null) {
245
+ const exportedNames = match[1];
246
+ const importPath = match[2];
247
+
248
+ // Resolve the import path
249
+ const barrelDir = dirname(absoluteBarrelPath);
250
+ let resolvedPath = resolve(barrelDir, importPath);
251
+
252
+ // Add extension if needed
253
+ if (!resolvedPath.endsWith('.tsx') && !resolvedPath.endsWith('.ts')) {
254
+ if (existsSync(`${resolvedPath}.tsx`)) {
255
+ resolvedPath = `${resolvedPath}.tsx`;
256
+ } else if (existsSync(`${resolvedPath}.ts`)) {
257
+ resolvedPath = `${resolvedPath}.ts`;
258
+ } else if (existsSync(`${resolvedPath}/index.tsx`)) {
259
+ resolvedPath = `${resolvedPath}/index.tsx`;
260
+ } else if (existsSync(`${resolvedPath}/index.ts`)) {
261
+ resolvedPath = `${resolvedPath}/index.ts`;
262
+ }
263
+ }
264
+
265
+ if (!existsSync(resolvedPath)) {
266
+ continue;
267
+ }
268
+
269
+ if (exportedNames) {
270
+ // Named exports: { Button, Card }
271
+ const names = exportedNames.split(',').map((n) => n.trim().split(/\s+as\s+/)[0].trim());
272
+ for (const name of names) {
273
+ if (/^[A-Z]/.test(name)) {
274
+ const relativePath = resolvedPath.replace(configDir + '/', '');
275
+ components.push({
276
+ name,
277
+ sourcePath: resolvedPath,
278
+ relativePath,
279
+ });
280
+ }
281
+ }
282
+ } else {
283
+ // Star export: export * from './Component'
284
+ const name = extractComponentName(importPath);
285
+ if (/^[A-Z]/.test(name)) {
286
+ const relativePath = resolvedPath.replace(configDir + '/', '');
287
+ components.push({
288
+ name,
289
+ sourcePath: resolvedPath,
290
+ relativePath,
291
+ });
292
+ }
293
+ }
294
+ }
295
+
296
+ return components;
297
+ }
298
+
299
+ /**
300
+ * Discover all components using multiple strategies
301
+ */
302
+ export async function discoverAllComponents(
303
+ configDir: string,
304
+ options: {
305
+ patterns?: string[];
306
+ exclude?: string[];
307
+ barrelFiles?: string[];
308
+ } = {}
309
+ ): Promise<DiscoveredComponent[]> {
310
+ const componentsMap = new Map<string, DiscoveredComponent>();
311
+
312
+ // Discover from source files
313
+ const sourceComponents = await discoverComponentsFromSource(
314
+ configDir,
315
+ options.patterns,
316
+ options.exclude
317
+ );
318
+
319
+ for (const comp of sourceComponents) {
320
+ componentsMap.set(comp.name, comp);
321
+ }
322
+
323
+ // Discover from barrel files if specified
324
+ if (options.barrelFiles && options.barrelFiles.length > 0) {
325
+ for (const barrelFile of options.barrelFiles) {
326
+ const barrelComponents = await discoverComponentsFromBarrel(barrelFile, configDir);
327
+ for (const comp of barrelComponents) {
328
+ // Only add if not already found
329
+ if (!componentsMap.has(comp.name)) {
330
+ componentsMap.set(comp.name, comp);
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ return Array.from(componentsMap.values()).sort((a, b) => a.name.localeCompare(b.name));
337
+ }
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Figma property mapping DSL
3
+ *
4
+ * Provides helpers for mapping Figma component properties to code props.
5
+ * Inspired by Figma Code Connect's API.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { defineSegment, figma } from '@fragments/core';
10
+ *
11
+ * export default defineSegment({
12
+ * component: Button,
13
+ * meta: {
14
+ * name: 'Button',
15
+ * description: 'Primary action trigger',
16
+ * category: 'actions',
17
+ * figma: 'https://figma.com/file/abc/Design?node-id=1-2',
18
+ * figmaProps: {
19
+ * children: figma.string('Label'),
20
+ * disabled: figma.boolean('Disabled'),
21
+ * variant: figma.enum('Type', {
22
+ * 'Primary': 'primary',
23
+ * 'Secondary': 'secondary',
24
+ * }),
25
+ * },
26
+ * },
27
+ * // ...
28
+ * });
29
+ * ```
30
+ */
31
+
32
+ import type {
33
+ FigmaStringMapping,
34
+ FigmaBooleanMapping,
35
+ FigmaEnumMapping,
36
+ FigmaInstanceMapping,
37
+ FigmaChildrenMapping,
38
+ FigmaTextContentMapping,
39
+ } from './types.js';
40
+
41
+ /**
42
+ * Map a Figma text property to a string prop.
43
+ *
44
+ * @param figmaProperty - The name of the text property in Figma
45
+ * @returns A string mapping descriptor
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * figmaProps: {
50
+ * label: figma.string('Button Text'),
51
+ * placeholder: figma.string('Placeholder'),
52
+ * }
53
+ * ```
54
+ */
55
+ function string(figmaProperty: string): FigmaStringMapping {
56
+ return {
57
+ __type: 'figma-string',
58
+ figmaProperty,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Map a Figma boolean property to a boolean prop.
64
+ * Optionally map true/false to different values.
65
+ *
66
+ * @param figmaProperty - The name of the boolean property in Figma
67
+ * @param valueMapping - Optional mapping of true/false to other values
68
+ * @returns A boolean mapping descriptor
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * figmaProps: {
73
+ * disabled: figma.boolean('Disabled'),
74
+ * // Map boolean to string values
75
+ * size: figma.boolean('Large', { true: 'lg', false: 'md' }),
76
+ * }
77
+ * ```
78
+ */
79
+ function boolean(
80
+ figmaProperty: string,
81
+ valueMapping?: { true: unknown; false: unknown }
82
+ ): FigmaBooleanMapping {
83
+ return {
84
+ __type: 'figma-boolean',
85
+ figmaProperty,
86
+ valueMapping,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Map a Figma variant property to an enum prop.
92
+ *
93
+ * @param figmaProperty - The name of the variant property in Figma
94
+ * @param valueMapping - Mapping of Figma values to code values
95
+ * @returns An enum mapping descriptor
96
+ *
97
+ * @example
98
+ * ```tsx
99
+ * figmaProps: {
100
+ * variant: figma.enum('Type', {
101
+ * 'Primary': 'primary',
102
+ * 'Secondary': 'secondary',
103
+ * 'Outline': 'outline',
104
+ * }),
105
+ * size: figma.enum('Size', {
106
+ * 'Small': 'sm',
107
+ * 'Medium': 'md',
108
+ * 'Large': 'lg',
109
+ * }),
110
+ * }
111
+ * ```
112
+ */
113
+ function enumValue<T extends Record<string, unknown>>(
114
+ figmaProperty: string,
115
+ valueMapping: T
116
+ ): FigmaEnumMapping {
117
+ return {
118
+ __type: 'figma-enum',
119
+ figmaProperty,
120
+ valueMapping,
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Reference a nested Figma component instance.
126
+ * Use this when a prop accepts a component that's represented
127
+ * as an instance swap in Figma.
128
+ *
129
+ * @param figmaProperty - The name of the instance property in Figma
130
+ * @returns An instance mapping descriptor
131
+ *
132
+ * @example
133
+ * ```tsx
134
+ * figmaProps: {
135
+ * icon: figma.instance('Icon'),
136
+ * avatar: figma.instance('Avatar'),
137
+ * }
138
+ * ```
139
+ */
140
+ function instance(figmaProperty: string): FigmaInstanceMapping {
141
+ return {
142
+ __type: 'figma-instance',
143
+ figmaProperty,
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Render children from specific Figma layers.
149
+ * Use this when children are represented as named layers in Figma.
150
+ *
151
+ * @param layers - Array of layer names to include as children
152
+ * @returns A children mapping descriptor
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * figmaProps: {
157
+ * children: figma.children(['Title', 'Description', 'Actions']),
158
+ * }
159
+ * ```
160
+ */
161
+ function children(layers: string[]): FigmaChildrenMapping {
162
+ return {
163
+ __type: 'figma-children',
164
+ layers,
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Extract text content from a Figma text layer.
170
+ * Use this when a prop should be the actual text from a layer.
171
+ *
172
+ * @param layer - The name of the text layer in Figma
173
+ * @returns A text content mapping descriptor
174
+ *
175
+ * @example
176
+ * ```tsx
177
+ * figmaProps: {
178
+ * title: figma.textContent('Header Text'),
179
+ * description: figma.textContent('Body Text'),
180
+ * }
181
+ * ```
182
+ */
183
+ function textContent(layer: string): FigmaTextContentMapping {
184
+ return {
185
+ __type: 'figma-text-content',
186
+ layer,
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Figma property mapping helpers.
192
+ *
193
+ * Use these to define how Figma properties map to your component props.
194
+ * The mappings are used for:
195
+ * - Generating accurate code snippets in Figma Dev Mode
196
+ * - AI agents understanding the design-to-code relationship
197
+ * - Automated design verification
198
+ */
199
+ export const figma = {
200
+ string,
201
+ boolean,
202
+ enum: enumValue,
203
+ instance,
204
+ children,
205
+ textContent,
206
+ } as const;
207
+
208
+ /**
209
+ * Helper type to check if a value is a Figma prop mapping
210
+ */
211
+ export function isFigmaPropMapping(
212
+ value: unknown
213
+ ): value is FigmaStringMapping | FigmaBooleanMapping | FigmaEnumMapping | FigmaInstanceMapping | FigmaChildrenMapping | FigmaTextContentMapping {
214
+ if (typeof value !== 'object' || value === null || !('__type' in value)) {
215
+ return false;
216
+ }
217
+ const typeValue = (value as Record<string, unknown>).__type;
218
+ return typeof typeValue === 'string' && typeValue.startsWith('figma-');
219
+ }
220
+
221
+ /**
222
+ * Resolve a Figma prop mapping to an actual value given Figma property values.
223
+ *
224
+ * @param mapping - The Figma prop mapping
225
+ * @param figmaValues - Object containing Figma property values
226
+ * @returns The resolved value for the code prop
227
+ */
228
+ export function resolveFigmaMapping(
229
+ mapping: FigmaStringMapping | FigmaBooleanMapping | FigmaEnumMapping | FigmaInstanceMapping | FigmaChildrenMapping | FigmaTextContentMapping,
230
+ figmaValues: Record<string, unknown>
231
+ ): unknown {
232
+ switch (mapping.__type) {
233
+ case 'figma-string':
234
+ return figmaValues[mapping.figmaProperty] ?? '';
235
+
236
+ case 'figma-boolean': {
237
+ const boolValue = figmaValues[mapping.figmaProperty] as boolean;
238
+ if (mapping.valueMapping) {
239
+ return boolValue ? mapping.valueMapping.true : mapping.valueMapping.false;
240
+ }
241
+ return boolValue;
242
+ }
243
+
244
+ case 'figma-enum': {
245
+ const enumKey = figmaValues[mapping.figmaProperty] as string;
246
+ return mapping.valueMapping[enumKey] ?? enumKey;
247
+ }
248
+
249
+ case 'figma-instance':
250
+ // Instance mappings return the instance reference
251
+ return figmaValues[mapping.figmaProperty];
252
+
253
+ case 'figma-children':
254
+ // Children mappings return array of layer contents
255
+ return mapping.layers.map((layer) => figmaValues[layer]);
256
+
257
+ case 'figma-text-content':
258
+ return figmaValues[mapping.layer] ?? '';
259
+
260
+ default:
261
+ return undefined;
262
+ }
263
+ }