@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,313 @@
1
+ /**
2
+ * fragments add - Scaffold a new component with fragment file
3
+ */
4
+
5
+ import { writeFile, mkdir, access } from 'node:fs/promises';
6
+ import { resolve, join, relative } from 'node:path';
7
+ import pc from 'picocolors';
8
+ import { BRAND } from '../core/index.js';
9
+
10
+ /**
11
+ * Options for add command
12
+ */
13
+ export interface AddOptions {
14
+ /** Component category */
15
+ category?: string;
16
+ /** Output directory */
17
+ dir?: string;
18
+ /** Template to use (action, form-input, layout, display) */
19
+ template?: string;
20
+ /** Only generate fragment file, skip component stub */
21
+ component?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Result of add command
26
+ */
27
+ export interface AddResult {
28
+ success: boolean;
29
+ componentPath?: string;
30
+ segmentPath: string;
31
+ indexPath?: string;
32
+ }
33
+
34
+ /**
35
+ * Run the add command
36
+ */
37
+ export async function add(
38
+ name: string | undefined,
39
+ options: AddOptions = {}
40
+ ): Promise<AddResult> {
41
+ console.log(pc.cyan(`\n${BRAND.name} Component Scaffold\n`));
42
+
43
+ let componentName = name;
44
+ let category = options.category;
45
+ let template = options.template;
46
+ let dir = options.dir;
47
+ const generateComponent = options.component !== false;
48
+
49
+ // If any required values are missing, go interactive
50
+ if (!componentName || !category || !template || !dir) {
51
+ const { input, select } = await import('@inquirer/prompts');
52
+
53
+ try {
54
+ // Component name
55
+ if (!componentName) {
56
+ componentName = await input({
57
+ message: 'Component name:',
58
+ validate: (value) => {
59
+ if (!value.trim()) return 'Component name is required';
60
+ if (!/^[A-Za-z][A-Za-z0-9]*$/.test(value)) return 'Use PascalCase (e.g., Button, TextField)';
61
+ return true;
62
+ },
63
+ });
64
+ }
65
+
66
+ // Template type
67
+ if (!template) {
68
+ template = await select({
69
+ message: 'What type of component is this?',
70
+ choices: [
71
+ { name: 'Display - Shows information or status', value: 'display' },
72
+ { name: 'Action - Triggers an action (button, link)', value: 'action' },
73
+ { name: 'Form Input - Accepts user input', value: 'form-input' },
74
+ { name: 'Layout - Organizes content structure', value: 'layout' },
75
+ ],
76
+ default: 'display',
77
+ });
78
+ }
79
+
80
+ // Category
81
+ if (!category) {
82
+ category = await input({
83
+ message: 'Category:',
84
+ default: template === 'form-input' ? 'forms' : template === 'layout' ? 'layout' : 'components',
85
+ });
86
+ }
87
+
88
+ // Output directory
89
+ if (!dir) {
90
+ dir = await input({
91
+ message: 'Output directory:',
92
+ default: 'src/components',
93
+ });
94
+ }
95
+ } catch {
96
+ // User cancelled (Ctrl+C)
97
+ console.log(pc.dim('\nNo changes made.'));
98
+ process.exit(0);
99
+ }
100
+ }
101
+
102
+ // Normalize component name (PascalCase)
103
+ componentName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
104
+
105
+ // Apply defaults for non-interactive values
106
+ category = category || 'components';
107
+ template = template || 'display';
108
+ dir = dir || 'src/components';
109
+
110
+ // Determine output paths
111
+ const componentDir = resolve(process.cwd(), dir, componentName);
112
+ const componentFile = join(componentDir, `${componentName}.tsx`);
113
+ const segmentFile = join(componentDir, `${componentName}${BRAND.fileExtension}`);
114
+ const indexFile = join(componentDir, 'index.ts');
115
+
116
+ // Check if directory already exists
117
+ try {
118
+ await access(componentDir);
119
+ console.log(pc.yellow(`Directory already exists: ${relative(process.cwd(), componentDir)}`));
120
+ console.log(pc.dim('Use a different name or remove the existing directory.'));
121
+ process.exit(1);
122
+ } catch {
123
+ // Directory doesn't exist, proceed
124
+ }
125
+
126
+ // Create directory
127
+ await mkdir(componentDir, { recursive: true });
128
+
129
+ // Generate component stub (unless --no-component)
130
+ if (generateComponent) {
131
+ const componentCode = generateComponentStub(componentName, template);
132
+ await writeFile(componentFile, componentCode);
133
+ console.log(`${pc.green('✓')} Created ${relative(process.cwd(), componentFile)}`);
134
+ }
135
+
136
+ // Generate segment file
137
+ const segmentCode = generateSegmentStub(componentName, category, template);
138
+ await writeFile(segmentFile, segmentCode);
139
+ console.log(`${pc.green('✓')} Created ${relative(process.cwd(), segmentFile)}`);
140
+
141
+ // Generate index.ts
142
+ const indexCode = `export { ${componentName} } from './${componentName}.js';\n`;
143
+ await writeFile(indexFile, indexCode);
144
+ console.log(`${pc.green('✓')} Created ${relative(process.cwd(), indexFile)}`);
145
+
146
+ console.log(pc.green(`\n✓ Scaffolded ${componentName}\n`));
147
+
148
+ // Next steps
149
+ console.log(pc.dim('───────────────────────────────────────'));
150
+ console.log(pc.bold('\nNext steps:'));
151
+ if (generateComponent) {
152
+ console.log(` 1. Implement ${componentName}.tsx`);
153
+ }
154
+ console.log(` 2. Fill in usage.when and usage.whenNot in the fragment file`);
155
+ console.log(` 3. Add variants with different prop combinations`);
156
+ console.log(` 4. Run ${pc.cyan(`${BRAND.cliCommand} dev`)} to preview`);
157
+ console.log();
158
+
159
+ return {
160
+ success: true,
161
+ componentPath: generateComponent ? componentFile : undefined,
162
+ segmentPath: segmentFile,
163
+ indexPath: indexFile,
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Generate a React component stub based on template type.
169
+ */
170
+ function generateComponentStub(name: string, _template: string): string {
171
+ const propsInterface = `export interface ${name}Props {
172
+ /** Content to display */
173
+ children?: React.ReactNode;
174
+ /** Additional CSS classes */
175
+ className?: string;
176
+ }`;
177
+
178
+ return `import React from 'react';
179
+
180
+ ${propsInterface}
181
+
182
+ /**
183
+ * ${name} component
184
+ *
185
+ * TODO: Implement this component
186
+ */
187
+ export function ${name}({ children, className }: ${name}Props) {
188
+ return (
189
+ <div className={className}>
190
+ {children}
191
+ </div>
192
+ );
193
+ }
194
+ `;
195
+ }
196
+
197
+ /**
198
+ * Generate a segment stub based on template type.
199
+ */
200
+ function generateSegmentStub(name: string, category: string, template: string): string {
201
+ // Template-specific usage hints
202
+ const usageHints: Record<string, { when: string[]; whenNot: string[] }> = {
203
+ action: {
204
+ when: ['User needs to trigger an action', 'Form submission is required'],
205
+ whenNot: ['Navigation is needed (use Link)', 'Action is destructive without confirmation'],
206
+ },
207
+ 'form-input': {
208
+ when: ['User needs to enter data', 'Form field is required'],
209
+ whenNot: ['Display-only data is shown', 'Rich text editing is needed'],
210
+ },
211
+ layout: {
212
+ when: ['Content needs to be organized', 'Responsive layout is required'],
213
+ whenNot: ['Simple inline content is displayed', 'Scroll container is needed'],
214
+ },
215
+ display: {
216
+ when: ['Information needs to be presented', 'Status or feedback is shown'],
217
+ whenNot: ['User interaction is required', 'Data input is needed'],
218
+ },
219
+ };
220
+
221
+ const hints = usageHints[template] || usageHints.display;
222
+
223
+ // Template-specific scenario tags
224
+ const scenarioTagHints: Record<string, string[]> = {
225
+ action: ['action.primary', 'action.secondary', 'form.submit'],
226
+ 'form-input': ['form.input', 'form.field', 'form.text'],
227
+ layout: ['layout.container', 'layout.section', 'content.group'],
228
+ display: ['display.info', 'display.status', 'content.text'],
229
+ };
230
+
231
+ const scenarioTags = scenarioTagHints[template] || scenarioTagHints.display;
232
+
233
+ return `import React from 'react';
234
+ import { defineSegment } from '@fragments/core';
235
+ import { ${name} } from './index.js';
236
+
237
+ export default defineSegment({
238
+ component: ${name},
239
+
240
+ meta: {
241
+ name: '${name}',
242
+ description: 'TODO: Add description',
243
+ category: '${category}',
244
+ status: 'experimental',
245
+ tags: ['${category}'],
246
+ },
247
+
248
+ usage: {
249
+ when: [
250
+ '${hints.when[0]}',
251
+ '${hints.when[1]}',
252
+ ],
253
+ whenNot: [
254
+ '${hints.whenNot[0]}',
255
+ '${hints.whenNot[1]}',
256
+ ],
257
+ guidelines: [
258
+ // TODO: Add best practices
259
+ ],
260
+ accessibility: [
261
+ // TODO: Add accessibility guidelines
262
+ ],
263
+ },
264
+
265
+ props: {
266
+ children: {
267
+ type: 'node',
268
+ description: 'Content to display',
269
+ },
270
+ className: {
271
+ type: 'string',
272
+ description: 'Additional CSS classes',
273
+ },
274
+ // TODO: Add more props
275
+ },
276
+
277
+ relations: [
278
+ // TODO: Add related components
279
+ // { component: 'RelatedComponent', relationship: 'alternative', note: 'Use for...' },
280
+ ],
281
+
282
+ contract: {
283
+ propsSummary: [
284
+ 'children: ReactNode - content to display',
285
+ 'className: string - additional CSS classes',
286
+ // TODO: Add prop summaries
287
+ ],
288
+ scenarioTags: [
289
+ '${scenarioTags[0]}',
290
+ '${scenarioTags[1]}',
291
+ // TODO: Add scenario tags for AI agent queries
292
+ ],
293
+ a11yRules: [
294
+ // TODO: Add accessibility rule IDs
295
+ ],
296
+ },
297
+
298
+ variants: [
299
+ {
300
+ name: 'Default',
301
+ description: 'Default ${name} appearance',
302
+ render: () => <${name}>Example content</${name}>,
303
+ },
304
+ // TODO: Add more variants
305
+ // {
306
+ // name: 'WithProps',
307
+ // description: '${name} with additional props',
308
+ // render: () => <${name} someProp="value">Content</${name}>,
309
+ // },
310
+ ],
311
+ });
312
+ `;
313
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * fragments audit - Scan all fragments and show compliance metrics
3
+ *
4
+ * Uses the /fragments/compliance endpoint to get real token compliance data
5
+ * for all components in the design system.
6
+ */
7
+
8
+ import pc from 'picocolors';
9
+ import { BRAND } from '../core/index.js';
10
+ import { loadConfig } from '../core/node.js';
11
+ import {
12
+ createDevServerClient,
13
+ DevServerConnectionError,
14
+ type AuditResult,
15
+ } from '../shared/index.js';
16
+
17
+ /**
18
+ * Options for audit command
19
+ */
20
+ export interface AuditOptions {
21
+ /** Path to config file */
22
+ config?: string;
23
+ /** Output JSON format */
24
+ json?: boolean;
25
+ /** Sort field: compliance, name, hardcoded */
26
+ sort?: 'compliance' | 'name' | 'hardcoded';
27
+ /** Dev server port */
28
+ port?: number | string;
29
+ }
30
+
31
+ /**
32
+ * Summary of audit results
33
+ */
34
+ export interface AuditSummary {
35
+ totalComponents: number;
36
+ averageCompliance: number;
37
+ worstOffenders: AuditResult[];
38
+ components: AuditResult[];
39
+ stats: {
40
+ totalHardcoded: number;
41
+ totalTokenMismatches: number;
42
+ totalProperties: number;
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Run the audit command
48
+ */
49
+ export async function audit(options: AuditOptions = {}): Promise<AuditSummary> {
50
+ const { config: configPath, json = false, sort = 'compliance', port = 6006 } = options;
51
+
52
+ // Load config
53
+ await loadConfig(configPath);
54
+
55
+ const client = createDevServerClient(port);
56
+ const audits: AuditResult[] = [];
57
+
58
+ if (!json) {
59
+ console.log(pc.cyan(`\n${BRAND.name} Design System Audit\n`));
60
+ }
61
+
62
+ // Check if dev server is reachable
63
+ const isReachable = await client.ping();
64
+ if (!isReachable) {
65
+ throw new DevServerConnectionError(
66
+ `Cannot connect to dev server at http://localhost:${port}`,
67
+ `http://localhost:${port}`
68
+ );
69
+ }
70
+
71
+ // Fetch all fragments
72
+ const segments = await client.getSegments();
73
+
74
+ if (segments.length === 0) {
75
+ if (json) {
76
+ console.log(JSON.stringify({ error: 'No fragments found', components: [] }));
77
+ } else {
78
+ console.log(pc.yellow('No fragments found.\n'));
79
+ }
80
+ return {
81
+ totalComponents: 0,
82
+ averageCompliance: 100,
83
+ worstOffenders: [],
84
+ components: [],
85
+ stats: {
86
+ totalHardcoded: 0,
87
+ totalTokenMismatches: 0,
88
+ totalProperties: 0,
89
+ },
90
+ };
91
+ }
92
+
93
+ if (!json) {
94
+ console.log(pc.dim(`Auditing ${segments.length} component(s)...\n`));
95
+ }
96
+
97
+ // Audit each fragment
98
+ for (const seg of segments) {
99
+ try {
100
+ // Get real compliance from the server
101
+ const complianceResult = await client.getCompliance({
102
+ component: seg.name,
103
+ });
104
+
105
+ audits.push({
106
+ name: seg.name,
107
+ category: seg.category || 'uncategorized',
108
+ compliance: complianceResult.compliance,
109
+ hardcoded: complianceResult.hardcoded,
110
+ tokenMismatches: complianceResult.violations.filter(
111
+ v => v.issue.includes('mismatch')
112
+ ).length,
113
+ totalProperties: complianceResult.totalProperties,
114
+ });
115
+ } catch (error) {
116
+ // Handle individual component errors
117
+ audits.push({
118
+ name: seg.name,
119
+ category: seg.category || 'uncategorized',
120
+ compliance: 0,
121
+ hardcoded: 0,
122
+ tokenMismatches: 0,
123
+ totalProperties: 0,
124
+ });
125
+ }
126
+ }
127
+
128
+ // Sort results
129
+ audits.sort((a, b) => {
130
+ switch (sort) {
131
+ case 'name':
132
+ return a.name.localeCompare(b.name);
133
+ case 'hardcoded':
134
+ return b.hardcoded - a.hardcoded; // Most hardcoded first
135
+ case 'compliance':
136
+ default:
137
+ return a.compliance - b.compliance; // Worst first
138
+ }
139
+ });
140
+
141
+ const summary: AuditSummary = {
142
+ totalComponents: audits.length,
143
+ averageCompliance: audits.length > 0
144
+ ? Math.round(audits.reduce((sum, a) => sum + a.compliance, 0) / audits.length * 100) / 100
145
+ : 100,
146
+ worstOffenders: audits.slice(0, 5),
147
+ components: audits,
148
+ stats: {
149
+ totalHardcoded: audits.reduce((sum, a) => sum + a.hardcoded, 0),
150
+ totalTokenMismatches: audits.reduce((sum, a) => sum + a.tokenMismatches, 0),
151
+ totalProperties: audits.reduce((sum, a) => sum + a.totalProperties, 0),
152
+ },
153
+ };
154
+
155
+ if (json) {
156
+ // JSON output for CI/automation
157
+ console.log(JSON.stringify(summary, null, 2));
158
+ } else {
159
+ // Table output for humans
160
+ console.log(pc.bold('Component'.padEnd(30) + 'Compliance'.padEnd(12) + 'Hardcoded'.padEnd(12) + 'Properties'));
161
+ console.log(pc.dim('─'.repeat(66)));
162
+
163
+ for (const auditItem of audits) {
164
+ const complianceColor = auditItem.compliance >= 90
165
+ ? pc.green
166
+ : auditItem.compliance >= 70
167
+ ? pc.yellow
168
+ : pc.red;
169
+
170
+ console.log(
171
+ auditItem.name.padEnd(30) +
172
+ complianceColor(`${auditItem.compliance}%`.padEnd(12)) +
173
+ String(auditItem.hardcoded).padEnd(12) +
174
+ String(auditItem.totalProperties)
175
+ );
176
+ }
177
+
178
+ console.log(pc.dim('─'.repeat(66)));
179
+
180
+ console.log();
181
+ console.log(pc.bold('Summary:'));
182
+ console.log(` Total components: ${audits.length}`);
183
+ console.log(` Average compliance: ${summary.averageCompliance}%`);
184
+ console.log(` Total hardcoded values: ${summary.stats.totalHardcoded}`);
185
+ console.log(` Total properties checked: ${summary.stats.totalProperties}`);
186
+
187
+ if (audits.some(a => a.compliance < 100)) {
188
+ console.log();
189
+ console.log(pc.dim(`Run ${pc.cyan(`${BRAND.cliCommand} verify <component>`)} for detailed compliance info.`));
190
+ }
191
+ console.log();
192
+ }
193
+
194
+ return summary;
195
+ }