@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
package/src/diff.ts ADDED
@@ -0,0 +1,323 @@
1
+ import pc from 'picocolors';
2
+ import {
3
+ BRAND,
4
+ DEFAULTS,
5
+ type SegmentsConfig,
6
+ type SegmentDefinition,
7
+ type Theme,
8
+ } from './core/index.js';
9
+ import { discoverSegmentFiles, loadSegmentFile } from './core/node.js';
10
+ import {
11
+ BrowserPool,
12
+ CaptureEngine,
13
+ StorageManager,
14
+ DiffEngine,
15
+ formatMs,
16
+ type CaptureOptions,
17
+ type DiffResult,
18
+ } from './service/index.js';
19
+
20
+ /**
21
+ * Options for the diff command
22
+ */
23
+ export interface DiffCommandOptions {
24
+ /** Specific component to diff */
25
+ component?: string;
26
+
27
+ /** Specific variant to diff */
28
+ variant?: string;
29
+
30
+ /** Theme to compare */
31
+ theme?: Theme;
32
+
33
+ /** CI mode - exit 1 on differences */
34
+ ci?: boolean;
35
+
36
+ /** Diff threshold percentage */
37
+ threshold?: number;
38
+
39
+ /** Open diff images */
40
+ open?: boolean;
41
+ }
42
+
43
+ /**
44
+ * Single diff result with metadata
45
+ */
46
+ export interface VariantDiffResult {
47
+ component: string;
48
+ variant: string;
49
+ theme: Theme;
50
+ result: DiffResult;
51
+ diffImagePath?: string;
52
+ }
53
+
54
+ /**
55
+ * Result of the diff command
56
+ */
57
+ export interface DiffCommandResult {
58
+ success: boolean;
59
+ total: number;
60
+ passed: number;
61
+ failed: number;
62
+ missing: number;
63
+ results: VariantDiffResult[];
64
+ totalTimeMs: number;
65
+ }
66
+
67
+ /**
68
+ * Execute the diff command
69
+ */
70
+ export async function runDiffCommand(
71
+ config: SegmentsConfig,
72
+ configDir: string,
73
+ options: DiffCommandOptions = {}
74
+ ): Promise<DiffCommandResult> {
75
+ const startTime = Date.now();
76
+ const results: VariantDiffResult[] = [];
77
+
78
+ // Initialize storage
79
+ const storage = new StorageManager({
80
+ projectRoot: configDir,
81
+ viewport: config.screenshots?.viewport,
82
+ });
83
+ await storage.initialize();
84
+
85
+ // Initialize diff engine
86
+ const threshold = options.threshold ?? config.screenshots?.threshold ?? DEFAULTS.diffThreshold;
87
+ const diffEngine = new DiffEngine(threshold);
88
+
89
+ // Discover segments
90
+ const segmentFiles = await discoverSegmentFiles(config, configDir);
91
+
92
+ if (segmentFiles.length === 0) {
93
+ console.log(pc.yellow('No segment files found.'));
94
+ return {
95
+ success: true,
96
+ total: 0,
97
+ passed: 0,
98
+ failed: 0,
99
+ missing: 0,
100
+ results: [],
101
+ totalTimeMs: Date.now() - startTime,
102
+ };
103
+ }
104
+
105
+ // Load all segments
106
+ const segments: Array<{ path: string; segment: SegmentDefinition }> = [];
107
+
108
+ for (const file of segmentFiles) {
109
+ try {
110
+ const segment = await loadSegmentFile(file.absolutePath);
111
+ if (segment) {
112
+ segments.push({ path: file.relativePath, segment });
113
+ }
114
+ } catch {
115
+ // Skip failed loads
116
+ }
117
+ }
118
+
119
+ // Filter by component if specified
120
+ const filteredSegments = options.component
121
+ ? segments.filter((s) => s.segment.meta.name === options.component)
122
+ : segments;
123
+
124
+ if (options.component && filteredSegments.length === 0) {
125
+ console.log(pc.yellow(`Component "${options.component}" not found.`));
126
+ return {
127
+ success: false,
128
+ total: 0,
129
+ passed: 0,
130
+ failed: 0,
131
+ missing: 0,
132
+ results: [],
133
+ totalTimeMs: Date.now() - startTime,
134
+ };
135
+ }
136
+
137
+ // Build list of variants to diff
138
+ const variantsToDiff: Array<{
139
+ component: string;
140
+ variant: string;
141
+ }> = [];
142
+
143
+ for (const { segment } of filteredSegments) {
144
+ const variants = options.variant
145
+ ? segment.variants.filter((v) => v.name === options.variant)
146
+ : segment.variants;
147
+
148
+ for (const variant of variants) {
149
+ variantsToDiff.push({
150
+ component: segment.meta.name,
151
+ variant: variant.name,
152
+ });
153
+ }
154
+ }
155
+
156
+ if (variantsToDiff.length === 0) {
157
+ console.log(pc.yellow('No variants to compare.'));
158
+ return {
159
+ success: true,
160
+ total: 0,
161
+ passed: 0,
162
+ failed: 0,
163
+ missing: 0,
164
+ results: [],
165
+ totalTimeMs: Date.now() - startTime,
166
+ };
167
+ }
168
+
169
+ // Determine theme
170
+ const theme: Theme = options.theme ?? DEFAULTS.theme;
171
+ const viewport = config.screenshots?.viewport ?? DEFAULTS.viewport;
172
+
173
+ console.log(pc.cyan(`\n${BRAND.name} Diff\n`));
174
+ console.log(pc.dim(`Comparing against baselines (theme: ${theme}, threshold: ${threshold}%):\n`));
175
+
176
+ // Initialize browser pool
177
+ const pool = new BrowserPool({
178
+ viewport,
179
+ });
180
+
181
+ // Create capture engine
182
+ const viewerPort = DEFAULTS.port;
183
+ const baseUrl = `http://localhost:${viewerPort}`;
184
+ const captureEngine = new CaptureEngine(pool, baseUrl);
185
+
186
+ let passed = 0;
187
+ let failed = 0;
188
+ let missing = 0;
189
+
190
+ const captureOptions: CaptureOptions = {
191
+ theme,
192
+ viewport,
193
+ delay: config.screenshots?.delay ?? DEFAULTS.captureDelayMs,
194
+ };
195
+
196
+ try {
197
+ // Warm up the pool
198
+ await pool.warmup();
199
+
200
+ // Compare each variant
201
+ for (const { component, variant } of variantsToDiff) {
202
+ // Load baseline
203
+ const baseline = await storage.loadBaseline(component, variant, theme);
204
+
205
+ if (!baseline) {
206
+ console.log(
207
+ ` ${pc.yellow('?')} ${component}/${variant} ${pc.dim('(no baseline)')}`
208
+ );
209
+ missing++;
210
+ continue;
211
+ }
212
+
213
+ try {
214
+ // Capture current
215
+ const current = await captureEngine.captureVariant(
216
+ component,
217
+ variant,
218
+ captureOptions
219
+ );
220
+
221
+ // Quick hash check
222
+ if (diffEngine.areIdentical(current, baseline)) {
223
+ console.log(` ${pc.green('✓')} ${component}/${variant} ${pc.dim('0.0%')}`);
224
+ results.push({
225
+ component,
226
+ variant,
227
+ theme,
228
+ result: {
229
+ matches: true,
230
+ diffPercentage: 0,
231
+ diffPixelCount: 0,
232
+ totalPixels: current.viewport.width * current.viewport.height,
233
+ changedRegions: [],
234
+ diffTimeMs: 0,
235
+ },
236
+ });
237
+ passed++;
238
+ continue;
239
+ }
240
+
241
+ // Full diff
242
+ const diffResult = diffEngine.compare(current, baseline, { threshold });
243
+
244
+ if (diffResult.matches) {
245
+ console.log(
246
+ ` ${pc.green('✓')} ${component}/${variant} ${pc.dim(`${diffResult.diffPercentage}%`)}`
247
+ );
248
+ passed++;
249
+ } else {
250
+ // Save diff image
251
+ let diffImagePath: string | undefined;
252
+ if (diffResult.diffImage) {
253
+ diffImagePath = await storage.saveDiff(
254
+ component,
255
+ variant,
256
+ theme,
257
+ diffResult.diffImage
258
+ );
259
+ }
260
+
261
+ console.log(
262
+ ` ${pc.red('✗')} ${component}/${variant} ${pc.yellow(`${diffResult.diffPercentage}%`)}` +
263
+ (diffImagePath ? pc.dim(` → ${diffImagePath}`) : '')
264
+ );
265
+ failed++;
266
+
267
+ results.push({
268
+ component,
269
+ variant,
270
+ theme,
271
+ result: diffResult,
272
+ diffImagePath,
273
+ });
274
+ continue;
275
+ }
276
+
277
+ results.push({
278
+ component,
279
+ variant,
280
+ theme,
281
+ result: diffResult,
282
+ });
283
+ } catch (error) {
284
+ const errorMsg = error instanceof Error ? error.message : String(error);
285
+ console.log(` ${pc.red('!')} ${component}/${variant} ${pc.dim(errorMsg)}`);
286
+ failed++;
287
+ }
288
+ }
289
+ } finally {
290
+ // Shutdown browser pool
291
+ await pool.shutdown();
292
+ }
293
+
294
+ const totalTimeMs = Date.now() - startTime;
295
+ const total = passed + failed + missing;
296
+
297
+ // Print summary
298
+ console.log();
299
+ if (failed === 0 && missing === 0) {
300
+ console.log(pc.green(`✓ All ${passed} variant(s) match baselines`));
301
+ } else if (failed > 0) {
302
+ console.log(pc.red(`✗ ${failed} variant(s) differ from baselines`));
303
+ }
304
+
305
+ if (missing > 0) {
306
+ console.log(pc.yellow(` ${missing} variant(s) have no baseline (run \`${BRAND.cliCommand} screenshot\`)`));
307
+ }
308
+
309
+ console.log(pc.dim(` Completed in ${formatMs(totalTimeMs)}\n`));
310
+
311
+ // In CI mode, exit with error if any failed
312
+ const success = failed === 0;
313
+
314
+ return {
315
+ success,
316
+ total,
317
+ passed,
318
+ failed,
319
+ missing,
320
+ results,
321
+ totalTimeMs,
322
+ };
323
+ }
package/src/index.ts ADDED
@@ -0,0 +1,43 @@
1
+ // Re-export from core/node (Node.js-only APIs)
2
+ export {
3
+ loadConfig,
4
+ findConfigFile,
5
+ discoverSegmentFiles,
6
+ discoverComponentFiles,
7
+ extractComponentName,
8
+ } from "./core/node.js";
9
+ export type { DiscoveredFile } from "./core/node.js";
10
+
11
+ // Validators
12
+ export { validateSchema, validateCoverage, validateAll } from "./validators.js";
13
+ export type {
14
+ ValidationResult,
15
+ ValidationError,
16
+ ValidationWarning,
17
+ } from "./validators.js";
18
+
19
+ // Build
20
+ export { buildSegments } from "./build.js";
21
+ export type { BuildResult } from "./build.js";
22
+
23
+ // Screenshot
24
+ export { runScreenshotCommand } from "./screenshot.js";
25
+ export type {
26
+ ScreenshotCommandOptions,
27
+ ScreenshotResult,
28
+ } from "./screenshot.js";
29
+
30
+ // Diff
31
+ export { runDiffCommand } from "./diff.js";
32
+ export type {
33
+ DiffCommandOptions,
34
+ DiffCommandResult,
35
+ VariantDiffResult,
36
+ } from "./diff.js";
37
+
38
+ // Analyze
39
+ export { runAnalyzeCommand } from "./analyze.js";
40
+ export type { AnalyzeOptions, AnalyzeResult } from "./analyze.js";
41
+
42
+ // Static Viewer
43
+ export { generateStaticViewer, generateViewerFromJson } from "./static-viewer.js";
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { projectFields } from "../utils.js";
3
+
4
+ describe("projectFields", () => {
5
+ describe("basic field extraction", () => {
6
+ it("returns full object when no fields specified", () => {
7
+ const obj = { a: 1, b: 2, c: 3 };
8
+ expect(projectFields(obj, [])).toEqual(obj);
9
+ });
10
+
11
+ it("extracts single top-level field", () => {
12
+ const obj = { name: "Button", category: "actions", status: "stable" };
13
+ const result = projectFields(obj, ["name"]);
14
+ expect(result).toEqual({ name: "Button" });
15
+ });
16
+
17
+ it("extracts multiple top-level fields", () => {
18
+ const obj = { name: "Button", category: "actions", status: "stable" };
19
+ const result = projectFields(obj, ["name", "category"]);
20
+ expect(result).toEqual({ name: "Button", category: "actions" });
21
+ });
22
+
23
+ it("ignores non-existent fields", () => {
24
+ const obj = { name: "Button" };
25
+ const result = projectFields(obj, ["name", "nonexistent"]);
26
+ expect(result).toEqual({ name: "Button" });
27
+ });
28
+ });
29
+
30
+ describe("nested field extraction", () => {
31
+ it("extracts nested field with dot notation", () => {
32
+ const obj = {
33
+ meta: { name: "Button", category: "actions" },
34
+ usage: { when: ["action needed"], whenNot: ["static text"] },
35
+ };
36
+ const result = projectFields(obj, ["meta.name"]);
37
+ expect(result).toEqual({ meta: { name: "Button" } });
38
+ });
39
+
40
+ it("extracts multiple nested fields from same parent", () => {
41
+ const obj = {
42
+ meta: { name: "Button", category: "actions", status: "stable" },
43
+ };
44
+ const result = projectFields(obj, ["meta.name", "meta.category"]);
45
+ expect(result).toEqual({ meta: { name: "Button", category: "actions" } });
46
+ });
47
+
48
+ it("extracts deeply nested fields", () => {
49
+ const obj = {
50
+ config: {
51
+ theme: {
52
+ colors: { primary: "blue", secondary: "gray" },
53
+ },
54
+ },
55
+ };
56
+ const result = projectFields(obj, ["config.theme.colors.primary"]);
57
+ expect(result).toEqual({
58
+ config: { theme: { colors: { primary: "blue" } } },
59
+ });
60
+ });
61
+
62
+ it("extracts fields from different branches", () => {
63
+ const obj = {
64
+ meta: { name: "Button" },
65
+ usage: { when: ["action needed"] },
66
+ props: { variant: { type: "enum" } },
67
+ };
68
+ const result = projectFields(obj, ["meta.name", "usage.when"]);
69
+ expect(result).toEqual({
70
+ meta: { name: "Button" },
71
+ usage: { when: ["action needed"] },
72
+ });
73
+ });
74
+ });
75
+
76
+ describe("mixed top-level and nested", () => {
77
+ it("handles mix of top-level and nested fields", () => {
78
+ const obj = {
79
+ name: "Button",
80
+ meta: { category: "actions" },
81
+ variants: [{ name: "Primary" }],
82
+ };
83
+ const result = projectFields(obj, ["name", "meta.category"]);
84
+ expect(result).toEqual({
85
+ name: "Button",
86
+ meta: { category: "actions" },
87
+ });
88
+ });
89
+
90
+ it("extracts entire nested object when parent is specified", () => {
91
+ const obj = {
92
+ meta: { name: "Button", category: "actions" },
93
+ usage: { when: ["x"], whenNot: ["y"] },
94
+ };
95
+ const result = projectFields(obj, ["meta"]);
96
+ expect(result).toEqual({
97
+ meta: { name: "Button", category: "actions" },
98
+ });
99
+ });
100
+ });
101
+
102
+ describe("edge cases", () => {
103
+ it("handles null values in path (creates empty parent)", () => {
104
+ const obj = { meta: null };
105
+ const result = projectFields(obj as Record<string, unknown>, ["meta.name"]);
106
+ // When parent is null, we still create the path structure but value is undefined
107
+ expect(result).toEqual({ meta: {} });
108
+ });
109
+
110
+ it("handles undefined values in path (creates empty parent)", () => {
111
+ const obj = { meta: undefined };
112
+ const result = projectFields(obj as Record<string, unknown>, ["meta.name"]);
113
+ // When parent is undefined, we still create the path structure but value is undefined
114
+ expect(result).toEqual({ meta: {} });
115
+ });
116
+
117
+ it("handles arrays as values", () => {
118
+ const obj = {
119
+ usage: { when: ["use case 1", "use case 2"] },
120
+ };
121
+ const result = projectFields(obj, ["usage.when"]);
122
+ expect(result).toEqual({ usage: { when: ["use case 1", "use case 2"] } });
123
+ });
124
+
125
+ it("handles empty object", () => {
126
+ const result = projectFields({}, ["name"]);
127
+ expect(result).toEqual({});
128
+ });
129
+ });
130
+ });
package/src/mcp/bin.ts ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { startMcpServer } from './server.js';
3
+
4
+ // Parse command line arguments
5
+ const args = process.argv.slice(2);
6
+ let projectRoot = process.cwd();
7
+ let viewerUrl: string | undefined;
8
+
9
+ for (let i = 0; i < args.length; i++) {
10
+ const arg = args[i];
11
+
12
+ if (arg === '--project-root' || arg === '-p') {
13
+ projectRoot = args[++i] ?? projectRoot;
14
+ } else if (arg === '--viewer-url' || arg === '-u') {
15
+ viewerUrl = args[++i];
16
+ } else if (arg === '--help' || arg === '-h') {
17
+ console.log(`
18
+ Usage: fragments-mcp [options]
19
+
20
+ Options:
21
+ -p, --project-root <path> Project root directory (default: cwd)
22
+ -u, --viewer-url <url> Viewer URL (default: http://localhost:6006)
23
+ -h, --help Show this help message
24
+ `);
25
+ process.exit(0);
26
+ }
27
+ }
28
+
29
+ // Start server
30
+ startMcpServer({
31
+ projectRoot,
32
+ viewerUrl,
33
+ }).catch((error) => {
34
+ console.error('Failed to start MCP server:', error);
35
+ process.exit(1);
36
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fragments-sdk/mcp
3
+ *
4
+ * MCP server for AI agent integration with Fragments.
5
+ * Exposes tools for listing, querying, and verifying component fragments.
6
+ */
7
+
8
+ export { createMcpServer, startMcpServer, type McpServerConfig } from './server.js';