@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,485 @@
1
+ /**
2
+ * JSX Parser
3
+ *
4
+ * Parses JSX strings into a structured component tree that can be
5
+ * used for rendering multi-component compositions.
6
+ */
7
+
8
+ import { parse } from "@babel/parser";
9
+ import type {
10
+ JSXElement,
11
+ JSXFragment,
12
+ JSXText,
13
+ JSXExpressionContainer,
14
+ JSXAttribute,
15
+ JSXSpreadAttribute,
16
+ Expression,
17
+ Node,
18
+ } from "@babel/types";
19
+
20
+ /**
21
+ * Represents a parsed JSX component node
22
+ */
23
+ export interface ComponentNode {
24
+ /** Component name (e.g., "Button", "Card") or HTML element name (e.g., "div") */
25
+ name: string;
26
+ /** Whether this is an HTML element (lowercase) vs custom component (PascalCase) */
27
+ isHtmlElement: boolean;
28
+ /** Props/attributes passed to the component */
29
+ props: Record<string, unknown>;
30
+ /** Child nodes (can be ComponentNode, string, or expression) */
31
+ children: Array<ComponentNode | string>;
32
+ }
33
+
34
+ /**
35
+ * Result of parsing a JSX string
36
+ */
37
+ export interface ParseResult {
38
+ /** The root component tree */
39
+ tree: ComponentNode | ComponentNode[];
40
+ /** List of all unique component names found (excluding HTML elements) */
41
+ components: string[];
42
+ /** Any parsing errors */
43
+ errors: string[];
44
+ }
45
+
46
+ /**
47
+ * Parse a JSX string into a component tree.
48
+ *
49
+ * @param jsx - The JSX string to parse
50
+ * @returns ParseResult with the component tree and metadata
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const result = parseJSX('<Card><Button variant="primary">Click</Button></Card>');
55
+ * // result.tree = { name: 'Card', props: {}, children: [{ name: 'Button', props: { variant: 'primary' }, children: ['Click'] }] }
56
+ * // result.components = ['Card', 'Button']
57
+ * ```
58
+ */
59
+ export function parseJSX(jsx: string): ParseResult {
60
+ const errors: string[] = [];
61
+ const components = new Set<string>();
62
+
63
+ try {
64
+ // Wrap in a fragment to handle multiple root elements
65
+ const wrappedJsx = `<>${jsx}</>`;
66
+
67
+ const ast = parse(wrappedJsx, {
68
+ sourceType: "module",
69
+ plugins: ["jsx"],
70
+ });
71
+
72
+ // Find the expression statement containing our JSX
73
+ const program = ast.program;
74
+ const firstStatement = program.body[0];
75
+
76
+ if (firstStatement?.type !== "ExpressionStatement") {
77
+ return {
78
+ tree: [],
79
+ components: [],
80
+ errors: ["Invalid JSX: expected expression statement"],
81
+ };
82
+ }
83
+
84
+ const expression = firstStatement.expression;
85
+
86
+ if (expression.type !== "JSXFragment" && expression.type !== "JSXElement") {
87
+ return {
88
+ tree: [],
89
+ components: [],
90
+ errors: ["Invalid JSX: expected JSX element or fragment"],
91
+ };
92
+ }
93
+
94
+ // Parse the JSX tree
95
+ const parseNode = (
96
+ node: JSXElement | JSXFragment | JSXText | JSXExpressionContainer
97
+ ): ComponentNode | string | null => {
98
+ if (node.type === "JSXText") {
99
+ const text = node.value.trim();
100
+ return text || null;
101
+ }
102
+
103
+ if (node.type === "JSXExpressionContainer") {
104
+ // Handle expressions like {variable} or {condition && <Component />}
105
+ const exprValue = evaluateExpression(node.expression);
106
+ // Convert to string or return null if not renderable
107
+ if (exprValue === null || exprValue === undefined) {
108
+ return null;
109
+ }
110
+ if (typeof exprValue === "string") {
111
+ return exprValue;
112
+ }
113
+ // For non-string primitives, convert to string
114
+ return String(exprValue);
115
+ }
116
+
117
+ if (node.type === "JSXFragment") {
118
+ // For fragments, return children directly
119
+ const children = node.children
120
+ .map((child) => parseNode(child as JSXElement | JSXFragment | JSXText | JSXExpressionContainer))
121
+ .filter((c): c is ComponentNode | string => c !== null);
122
+
123
+ // If only one child, return it directly
124
+ if (children.length === 1 && typeof children[0] !== "string") {
125
+ return children[0];
126
+ }
127
+
128
+ // Return a special fragment node
129
+ return {
130
+ name: "Fragment",
131
+ isHtmlElement: false,
132
+ props: {},
133
+ children,
134
+ };
135
+ }
136
+
137
+ if (node.type === "JSXElement") {
138
+ const openingElement = node.openingElement;
139
+ let name: string;
140
+
141
+ if (openingElement.name.type === "JSXIdentifier") {
142
+ name = openingElement.name.name;
143
+ } else if (openingElement.name.type === "JSXMemberExpression") {
144
+ // Handle things like MyLib.Button
145
+ name = getMemberExpressionName(openingElement.name);
146
+ } else {
147
+ name = "Unknown";
148
+ }
149
+
150
+ // Check if it's an HTML element (lowercase first letter)
151
+ const isHtmlElement = /^[a-z]/.test(name);
152
+
153
+ // Track custom components
154
+ if (!isHtmlElement && name !== "Fragment") {
155
+ components.add(name);
156
+ }
157
+
158
+ // Parse props
159
+ const props: Record<string, unknown> = {};
160
+ for (const attr of openingElement.attributes) {
161
+ if (attr.type === "JSXAttribute") {
162
+ const propName = attr.name.type === "JSXIdentifier" ? attr.name.name : String(attr.name.name);
163
+ const propValue = parseAttributeValue(attr);
164
+ props[propName] = propValue;
165
+ } else if (attr.type === "JSXSpreadAttribute") {
166
+ // Handle spread attributes {...props}
167
+ const spreadValue = evaluateExpression(attr.argument);
168
+ if (typeof spreadValue === "object" && spreadValue !== null) {
169
+ Object.assign(props, spreadValue);
170
+ }
171
+ }
172
+ }
173
+
174
+ // Parse children
175
+ const children = node.children
176
+ .map((child) => parseNode(child as JSXElement | JSXFragment | JSXText | JSXExpressionContainer))
177
+ .filter((c): c is ComponentNode | string => c !== null);
178
+
179
+ return {
180
+ name,
181
+ isHtmlElement,
182
+ props,
183
+ children,
184
+ };
185
+ }
186
+
187
+ return null;
188
+ };
189
+
190
+ // Parse the root
191
+ const rootNode = parseNode(expression);
192
+
193
+ if (rootNode === null) {
194
+ return {
195
+ tree: [],
196
+ components: [],
197
+ errors: ["Failed to parse JSX"],
198
+ };
199
+ }
200
+
201
+ // If we got a Fragment with multiple children from our wrapper, unwrap it
202
+ if (
203
+ typeof rootNode !== "string" &&
204
+ rootNode.name === "Fragment" &&
205
+ rootNode.children.length > 0
206
+ ) {
207
+ // Check if all children are component nodes
208
+ const componentChildren = rootNode.children.filter(
209
+ (c): c is ComponentNode => typeof c !== "string"
210
+ );
211
+
212
+ if (componentChildren.length === 1) {
213
+ return {
214
+ tree: componentChildren[0],
215
+ components: Array.from(components).sort(),
216
+ errors,
217
+ };
218
+ }
219
+
220
+ if (componentChildren.length > 1) {
221
+ return {
222
+ tree: componentChildren,
223
+ components: Array.from(components).sort(),
224
+ errors,
225
+ };
226
+ }
227
+ }
228
+
229
+ return {
230
+ tree: typeof rootNode === "string" ? [] : rootNode,
231
+ components: Array.from(components).sort(),
232
+ errors,
233
+ };
234
+ } catch (error) {
235
+ const message = error instanceof Error ? error.message : "Unknown parse error";
236
+ return {
237
+ tree: [],
238
+ components: [],
239
+ errors: [message],
240
+ };
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Get the full name from a JSX member expression (e.g., MyLib.Button)
246
+ */
247
+ function getMemberExpressionName(node: any): string {
248
+ if (node.type === "JSXIdentifier") {
249
+ return node.name;
250
+ }
251
+ if (node.type === "JSXMemberExpression") {
252
+ return `${getMemberExpressionName(node.object)}.${node.property.name}`;
253
+ }
254
+ return "Unknown";
255
+ }
256
+
257
+ /**
258
+ * Parse a JSX attribute value
259
+ */
260
+ function parseAttributeValue(attr: JSXAttribute): unknown {
261
+ const value = attr.value;
262
+
263
+ // Boolean attribute (no value means true)
264
+ if (value === null || value === undefined) {
265
+ return true;
266
+ }
267
+
268
+ // String literal
269
+ if (value.type === "StringLiteral") {
270
+ return (value as { value: string }).value;
271
+ }
272
+
273
+ // Expression container {value}
274
+ if (value.type === "JSXExpressionContainer") {
275
+ return evaluateExpression((value as JSXExpressionContainer).expression);
276
+ }
277
+
278
+ // JSX element as attribute value
279
+ if (value.type === "JSXElement" || value.type === "JSXFragment") {
280
+ // This would be something like <Component prop={<Other />} />
281
+ // We'll return a string representation for now
282
+ return "[JSX Element]";
283
+ }
284
+
285
+ return null;
286
+ }
287
+
288
+ /**
289
+ * Evaluate a JavaScript expression from JSX.
290
+ * This handles common cases but is intentionally limited for safety.
291
+ */
292
+ function evaluateExpression(expr: Expression | Node): unknown {
293
+ if (!expr || expr.type === "JSXEmptyExpression") {
294
+ return null;
295
+ }
296
+
297
+ switch (expr.type) {
298
+ case "StringLiteral":
299
+ return expr.value;
300
+
301
+ case "NumericLiteral":
302
+ return expr.value;
303
+
304
+ case "BooleanLiteral":
305
+ return expr.value;
306
+
307
+ case "NullLiteral":
308
+ return null;
309
+
310
+ case "Identifier":
311
+ // Handle special identifiers
312
+ if (expr.name === "undefined") return undefined;
313
+ if (expr.name === "true") return true;
314
+ if (expr.name === "false") return false;
315
+ // Return the identifier name as a placeholder
316
+ return `{${expr.name}}`;
317
+
318
+ case "TemplateLiteral":
319
+ // Handle template literals with no expressions
320
+ if (expr.expressions.length === 0 && expr.quasis.length === 1) {
321
+ return expr.quasis[0].value.cooked || expr.quasis[0].value.raw;
322
+ }
323
+ // For template literals with expressions, return a placeholder
324
+ return "[Template Literal]";
325
+
326
+ case "ObjectExpression":
327
+ // Parse object literals like {{ key: 'value' }}
328
+ const obj: Record<string, unknown> = {};
329
+ for (const prop of expr.properties) {
330
+ if (prop.type === "ObjectProperty") {
331
+ const key =
332
+ prop.key.type === "Identifier"
333
+ ? prop.key.name
334
+ : prop.key.type === "StringLiteral"
335
+ ? prop.key.value
336
+ : String(prop.key);
337
+ obj[key] = evaluateExpression(prop.value as Expression);
338
+ }
339
+ }
340
+ return obj;
341
+
342
+ case "ArrayExpression":
343
+ // Parse array literals like {[1, 2, 3]}
344
+ return expr.elements.map((el) =>
345
+ el ? evaluateExpression(el as Expression) : null
346
+ );
347
+
348
+ case "ArrowFunctionExpression":
349
+ case "FunctionExpression":
350
+ // Return a placeholder for functions
351
+ return "[Function]";
352
+
353
+ case "UnaryExpression":
354
+ // Handle negation like {-5}
355
+ if (expr.operator === "-" && expr.argument.type === "NumericLiteral") {
356
+ return -expr.argument.value;
357
+ }
358
+ if (expr.operator === "!" && expr.argument.type === "BooleanLiteral") {
359
+ return !expr.argument.value;
360
+ }
361
+ return null;
362
+
363
+ case "ConditionalExpression":
364
+ // Ternary operator - return a placeholder
365
+ return "[Conditional]";
366
+
367
+ case "LogicalExpression":
368
+ // && or || - return a placeholder
369
+ return "[Logical Expression]";
370
+
371
+ case "MemberExpression":
372
+ // Something like obj.prop - return placeholder
373
+ return "[Member Expression]";
374
+
375
+ case "CallExpression":
376
+ // Function call - return placeholder
377
+ return "[Function Call]";
378
+
379
+ default:
380
+ return null;
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Extract all component names from a parsed tree
386
+ */
387
+ export function extractComponents(tree: ComponentNode | ComponentNode[]): string[] {
388
+ const components = new Set<string>();
389
+
390
+ const visit = (node: ComponentNode) => {
391
+ if (!node.isHtmlElement && node.name !== "Fragment") {
392
+ components.add(node.name);
393
+ }
394
+ for (const child of node.children) {
395
+ if (typeof child !== "string") {
396
+ visit(child);
397
+ }
398
+ }
399
+ };
400
+
401
+ if (Array.isArray(tree)) {
402
+ for (const node of tree) {
403
+ visit(node);
404
+ }
405
+ } else {
406
+ visit(tree);
407
+ }
408
+
409
+ return Array.from(components).sort();
410
+ }
411
+
412
+ /**
413
+ * Validate that all components in the tree exist in the available components
414
+ */
415
+ export function validateComponents(
416
+ tree: ComponentNode | ComponentNode[],
417
+ availableComponents: string[]
418
+ ): { valid: boolean; missing: string[]; suggestions: Map<string, string[]> } {
419
+ const used = extractComponents(tree);
420
+ const available = new Set(availableComponents.map((c) => c.toLowerCase()));
421
+ const availableOriginal = new Map(
422
+ availableComponents.map((c) => [c.toLowerCase(), c])
423
+ );
424
+
425
+ const missing: string[] = [];
426
+ const suggestions = new Map<string, string[]>();
427
+
428
+ for (const component of used) {
429
+ const lower = component.toLowerCase();
430
+ if (!available.has(lower)) {
431
+ missing.push(component);
432
+
433
+ // Find similar components for suggestions
434
+ const similar = availableComponents.filter((c) => {
435
+ const clower = c.toLowerCase();
436
+ // Check for partial matches
437
+ return (
438
+ clower.includes(lower) ||
439
+ lower.includes(clower) ||
440
+ levenshteinDistance(lower, clower) <= 2
441
+ );
442
+ });
443
+
444
+ if (similar.length > 0) {
445
+ suggestions.set(component, similar.slice(0, 3));
446
+ }
447
+ }
448
+ }
449
+
450
+ return {
451
+ valid: missing.length === 0,
452
+ missing,
453
+ suggestions,
454
+ };
455
+ }
456
+
457
+ /**
458
+ * Simple Levenshtein distance for fuzzy matching
459
+ */
460
+ function levenshteinDistance(a: string, b: string): number {
461
+ const matrix: number[][] = [];
462
+
463
+ for (let i = 0; i <= b.length; i++) {
464
+ matrix[i] = [i];
465
+ }
466
+ for (let j = 0; j <= a.length; j++) {
467
+ matrix[0][j] = j;
468
+ }
469
+
470
+ for (let i = 1; i <= b.length; i++) {
471
+ for (let j = 1; j <= a.length; j++) {
472
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
473
+ matrix[i][j] = matrix[i - 1][j - 1];
474
+ } else {
475
+ matrix[i][j] = Math.min(
476
+ matrix[i - 1][j - 1] + 1,
477
+ matrix[i][j - 1] + 1,
478
+ matrix[i - 1][j] + 1
479
+ );
480
+ }
481
+ }
482
+ }
483
+
484
+ return matrix[b.length][a.length];
485
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Preview Frame Entry Point
3
+ *
4
+ * This is the entry point for the isolated iframe that renders component previews.
5
+ * It runs inside the iframe and receives render requests from the parent window
6
+ * via postMessage.
7
+ */
8
+
9
+ import { createRoot } from 'react-dom/client';
10
+ import { PreviewFrameHost } from './components/PreviewFrameHost.js';
11
+
12
+ // Mount the preview host
13
+ const rootElement = document.getElementById('preview-root');
14
+
15
+ if (rootElement) {
16
+ const root = createRoot(rootElement);
17
+ root.render(<PreviewFrameHost />);
18
+ }
19
+
20
+ // HMR support
21
+ // @ts-expect-error Vite HMR types
22
+ if (import.meta.hot) {
23
+ // @ts-expect-error Vite HMR types
24
+ import.meta.hot.accept();
25
+ }
@@ -0,0 +1,109 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Fragment Preview</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ <style>
14
+ /* Reset and base styles for isolated preview */
15
+ *, *::before, *::after {
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ html, body {
20
+ margin: 0;
21
+ padding: 0;
22
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
23
+ -webkit-font-smoothing: antialiased;
24
+ -moz-osx-font-smoothing: grayscale;
25
+ width: 100%;
26
+ height: 100%;
27
+ overflow: auto;
28
+ }
29
+
30
+ body {
31
+ background: transparent;
32
+ min-height: 100%;
33
+ }
34
+
35
+ #preview-root {
36
+ width: 100%;
37
+ min-height: 100%;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ padding: 24px;
42
+ box-sizing: border-box;
43
+ }
44
+
45
+ /* Hide scrollbars but allow scrolling */
46
+ html::-webkit-scrollbar {
47
+ width: 0;
48
+ height: 0;
49
+ }
50
+
51
+ html {
52
+ scrollbar-width: none;
53
+ }
54
+
55
+ /* Error display styles */
56
+ .preview-error {
57
+ padding: 16px;
58
+ background: #fef2f2;
59
+ border: 1px solid #fecaca;
60
+ border-radius: 8px;
61
+ color: #dc2626;
62
+ font-size: 14px;
63
+ max-width: 400px;
64
+ }
65
+
66
+ .preview-error pre {
67
+ margin: 8px 0 0;
68
+ padding: 8px;
69
+ background: #fff;
70
+ border-radius: 4px;
71
+ overflow-x: auto;
72
+ font-size: 12px;
73
+ font-family: 'JetBrains Mono', ui-monospace, monospace;
74
+ }
75
+
76
+ /* Loading indicator */
77
+ .preview-loading {
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ gap: 8px;
82
+ color: #6b7280;
83
+ font-size: 14px;
84
+ }
85
+
86
+ .preview-loading .spinner {
87
+ width: 20px;
88
+ height: 20px;
89
+ border: 2px solid #e5e7eb;
90
+ border-top-color: #3b82f6;
91
+ border-radius: 50%;
92
+ animation: spin 0.8s linear infinite;
93
+ }
94
+
95
+ @keyframes spin {
96
+ to { transform: rotate(360deg); }
97
+ }
98
+ </style>
99
+ </head>
100
+ <body>
101
+ <div id="preview-root">
102
+ <div class="preview-loading">
103
+ <div class="spinner"></div>
104
+ <span>Loading preview...</span>
105
+ </div>
106
+ </div>
107
+ <script type="module" src="/src/preview-frame-entry.tsx"></script>
108
+ </body>
109
+ </html>
@@ -0,0 +1,68 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Segments Render</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ <style>
14
+ /* Reset and base styles for isolated rendering */
15
+ *, *::before, *::after {
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ html, body {
20
+ margin: 0;
21
+ padding: 0;
22
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
23
+ -webkit-font-smoothing: antialiased;
24
+ -moz-osx-font-smoothing: grayscale;
25
+ }
26
+
27
+ body {
28
+ background: #ffffff;
29
+ min-height: 100vh;
30
+ }
31
+
32
+ #render-root {
33
+ padding: 16px;
34
+ display: inline-block;
35
+ }
36
+
37
+ /* Signal that rendering is complete */
38
+ #render-root.ready {
39
+ /* Used by Playwright to know when to capture */
40
+ }
41
+
42
+ /* Error display */
43
+ .render-error {
44
+ padding: 16px;
45
+ background: #fef2f2;
46
+ border: 1px solid #fecaca;
47
+ border-radius: 8px;
48
+ color: #dc2626;
49
+ font-size: 14px;
50
+ }
51
+
52
+ .render-error pre {
53
+ margin: 8px 0 0;
54
+ padding: 8px;
55
+ background: #fff;
56
+ border-radius: 4px;
57
+ overflow-x: auto;
58
+ font-size: 12px;
59
+ font-family: 'JetBrains Mono', monospace;
60
+ }
61
+ </style>
62
+ <!-- PROJECT_STYLES_PLACEHOLDER -->
63
+ </head>
64
+ <body>
65
+ <div id="render-root"></div>
66
+ <!-- RENDER_SCRIPT_PLACEHOLDER -->
67
+ </body>
68
+ </html>