@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/build.ts ADDED
@@ -0,0 +1,248 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import { resolve, join } from "node:path";
3
+ import type {
4
+ SegmentsConfig,
5
+ CompiledSegmentsFile,
6
+ CompiledSegment,
7
+ CompiledRecipe,
8
+ } from "./core/index.js";
9
+ import { BRAND, compileRecipe } from "./core/index.js";
10
+ import type { RecipeDefinition } from "./core/index.js";
11
+ import {
12
+ discoverSegmentFiles,
13
+ discoverRecipeFiles,
14
+ parseSegmentFile,
15
+ loadSegmentFile,
16
+ generateRegistry,
17
+ generateContextMd,
18
+ } from "./core/node.js";
19
+
20
+ export interface BuildResult {
21
+ success: boolean;
22
+ outputPath: string;
23
+ segmentCount: number;
24
+ errors: Array<{ file: string; error: string }>;
25
+ warnings: Array<{ file: string; warning: string }>;
26
+ }
27
+
28
+ /**
29
+ * Build compiled fragments.json file for AI consumption.
30
+ *
31
+ * Uses AST parsing to extract metadata WITHOUT executing fragment files.
32
+ * This means the build works without any project dependencies installed.
33
+ */
34
+ export async function buildSegments(
35
+ config: SegmentsConfig,
36
+ configDir: string
37
+ ): Promise<BuildResult> {
38
+ const files = await discoverSegmentFiles(config, configDir);
39
+ const errors: Array<{ file: string; error: string }> = [];
40
+ const warnings: Array<{ file: string; warning: string }> = [];
41
+ const segments: CompiledSegmentsFile["segments"] = {};
42
+
43
+ for (const file of files) {
44
+ try {
45
+ // Read file content as text
46
+ const content = await readFile(file.absolutePath, "utf-8");
47
+
48
+ // Parse using AST (no execution)
49
+ const parsed = parseSegmentFile(content, file.relativePath);
50
+
51
+ // Collect warnings
52
+ for (const warning of parsed.warnings) {
53
+ warnings.push({ file: file.relativePath, warning });
54
+ }
55
+
56
+ // Check for required fields
57
+ if (!parsed.meta.name) {
58
+ errors.push({
59
+ file: file.relativePath,
60
+ error: "Missing meta.name in fragment definition",
61
+ });
62
+ continue;
63
+ }
64
+
65
+ // Build compiled fragment from parsed metadata
66
+ const compiled: CompiledSegment = {
67
+ filePath: file.relativePath,
68
+ meta: {
69
+ name: parsed.meta.name,
70
+ description: parsed.meta.description ?? "",
71
+ category: parsed.meta.category ?? "Uncategorized",
72
+ status: parsed.meta.status,
73
+ tags: parsed.meta.tags,
74
+ since: parsed.meta.since,
75
+ figma: parsed.meta.figma,
76
+ },
77
+ usage: {
78
+ when: parsed.usage.when ?? [],
79
+ whenNot: parsed.usage.whenNot ?? [],
80
+ guidelines: parsed.usage.guidelines,
81
+ accessibility: parsed.usage.accessibility,
82
+ },
83
+ props: Object.fromEntries(
84
+ Object.entries(parsed.props).map(([name, prop]) => [
85
+ name,
86
+ {
87
+ type: prop.type ?? "custom",
88
+ description: prop.description ?? "",
89
+ default: prop.default,
90
+ required: prop.required,
91
+ values: prop.values,
92
+ constraints: prop.constraints,
93
+ },
94
+ ])
95
+ ),
96
+ relations: parsed.relations.map((rel) => ({
97
+ component: rel.component,
98
+ relationship: rel.relationship as
99
+ | "alternative"
100
+ | "sibling"
101
+ | "parent"
102
+ | "child"
103
+ | "composition",
104
+ note: rel.note,
105
+ })),
106
+ variants: parsed.variants.map((v) => ({
107
+ name: v.name,
108
+ description: v.description,
109
+ ...(v.code && { code: v.code }),
110
+ ...(v.figma && { figma: v.figma }),
111
+ ...(v.args && { args: v.args }),
112
+ })),
113
+ };
114
+
115
+ segments[parsed.meta.name] = compiled;
116
+ } catch (error) {
117
+ errors.push({
118
+ file: file.relativePath,
119
+ error: error instanceof Error ? error.message : String(error),
120
+ });
121
+ }
122
+ }
123
+
124
+ // Discover and compile recipe files
125
+ const recipes: Record<string, CompiledRecipe> = {};
126
+ try {
127
+ const recipeFiles = await discoverRecipeFiles(configDir, config.exclude);
128
+ for (const file of recipeFiles) {
129
+ try {
130
+ // loadSegmentFile uses esbuild to bundle+evaluate, returns default export
131
+ // CJS/ESM interop may double-wrap the default export
132
+ let raw = await loadSegmentFile(file.absolutePath) as unknown as Record<string, unknown> | null;
133
+ // Unwrap double-default from CJS interop
134
+ if (raw && 'default' in raw && typeof raw.default === 'object') {
135
+ raw = raw.default as Record<string, unknown>;
136
+ }
137
+ const def = raw;
138
+ if (def && typeof def === 'object' && 'name' in def && 'code' in def && 'components' in def) {
139
+ const compiled = compileRecipe(def as unknown as RecipeDefinition, file.relativePath);
140
+ recipes[compiled.name] = compiled;
141
+ }
142
+ } catch (error) {
143
+ warnings.push({
144
+ file: file.relativePath,
145
+ warning: `Failed to load recipe: ${error instanceof Error ? error.message : String(error)}`,
146
+ });
147
+ }
148
+ }
149
+ } catch {
150
+ // Recipe discovery failure is non-fatal
151
+ }
152
+
153
+ const output: CompiledSegmentsFile = {
154
+ version: "1.0.0",
155
+ generatedAt: new Date().toISOString(),
156
+ segments,
157
+ ...(Object.keys(recipes).length > 0 && { recipes }),
158
+ };
159
+
160
+ const outputPath = resolve(configDir, config.outFile ?? BRAND.outFile);
161
+ await writeFile(outputPath, JSON.stringify(output));
162
+
163
+ return {
164
+ success: errors.length === 0,
165
+ outputPath,
166
+ segmentCount: Object.keys(segments).length,
167
+ errors,
168
+ warnings,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Result of building the .fragments directory
174
+ */
175
+ export interface FragmentsBuildResult {
176
+ success: boolean;
177
+ indexPath: string;
178
+ registryPath: string;
179
+ contextPath: string;
180
+ componentCount: number;
181
+ errors: Array<{ file: string; error: string }>;
182
+ warnings: Array<{ file: string; warning: string }>;
183
+ }
184
+
185
+ /**
186
+ * Build the .fragments/ directory with index.json, registry.json, and context.md
187
+ *
188
+ * This generates:
189
+ * - .fragments/index.json - Minimal name → path mapping (tiny, for quick lookups)
190
+ * - .fragments/registry.json - Component index with paths and enrichment references
191
+ * - .fragments/context.md - AI-ready consolidated context file
192
+ */
193
+ export async function buildFragmentsDir(
194
+ config: SegmentsConfig,
195
+ configDir: string
196
+ ): Promise<FragmentsBuildResult> {
197
+ const fragmentsDir = join(configDir, BRAND.dataDir);
198
+ const componentsDir = join(fragmentsDir, BRAND.componentsDir);
199
+
200
+ // Create directories
201
+ await mkdir(fragmentsDir, { recursive: true });
202
+ await mkdir(componentsDir, { recursive: true });
203
+
204
+ // Generate registry with config options
205
+ const registryResult = await generateRegistry({
206
+ projectRoot: configDir,
207
+ componentPatterns: config.components || ["src/**/*.tsx", "src/**/*.ts"],
208
+ storyPatterns: config.include || ["src/**/*.stories.tsx"],
209
+ fragmentsDir,
210
+ registryOptions: config.registry || {},
211
+ });
212
+
213
+ const errors = [...registryResult.errors];
214
+ const warnings = [...registryResult.warnings];
215
+
216
+ // Write index.json (minimal name → path)
217
+ const indexPath = join(fragmentsDir, "index.json");
218
+ await writeFile(indexPath, JSON.stringify(registryResult.index, null, 2));
219
+
220
+ // Write registry.json (full metadata)
221
+ const registryPath = join(fragmentsDir, BRAND.registryFile);
222
+ await writeFile(registryPath, JSON.stringify(registryResult.registry, null, 2));
223
+
224
+ // Generate context.md - focus on semantic knowledge, skip props (AI can read source)
225
+ const contextResult = generateContextMd(registryResult.registry, {
226
+ format: "markdown",
227
+ compact: false,
228
+ include: {
229
+ props: false, // AI can read TypeScript directly
230
+ relations: true,
231
+ code: false,
232
+ },
233
+ });
234
+
235
+ // Write context.md
236
+ const contextPath = join(fragmentsDir, BRAND.contextFile);
237
+ await writeFile(contextPath, contextResult.content);
238
+
239
+ return {
240
+ success: errors.length === 0,
241
+ indexPath,
242
+ registryPath,
243
+ contextPath,
244
+ componentCount: registryResult.registry.componentCount,
245
+ errors,
246
+ warnings,
247
+ };
248
+ }
@@ -0,0 +1,302 @@
1
+ /**
2
+ * fragments a11y - Run accessibility checks on all component variants
3
+ *
4
+ * Uses the /fragments/a11y endpoint to get axe-core accessibility results
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
+ } from '../shared/index.js';
15
+
16
+ /**
17
+ * Options for a11y command
18
+ */
19
+ export interface A11yOptions {
20
+ /** Path to config file */
21
+ config?: string;
22
+ /** Output JSON format */
23
+ json?: boolean;
24
+ /** CI mode - exit code 1 if any critical/serious violations */
25
+ ci?: boolean;
26
+ /** Check specific component only */
27
+ component?: string;
28
+ /** Dev server port */
29
+ port?: number | string;
30
+ }
31
+
32
+ /**
33
+ * A11y result for a single variant
34
+ */
35
+ export interface A11yVariantResult {
36
+ /** Variant name */
37
+ variant: string;
38
+ /** Number of violations */
39
+ violations: number;
40
+ /** Number of passes */
41
+ passes: number;
42
+ /** Number of incomplete checks */
43
+ incomplete: number;
44
+ /** Violation summary by severity */
45
+ summary: {
46
+ total: number;
47
+ critical: number;
48
+ serious: number;
49
+ moderate: number;
50
+ minor: number;
51
+ };
52
+ }
53
+
54
+ /**
55
+ * A11y result for a component
56
+ */
57
+ export interface A11yComponentResult {
58
+ /** Component name */
59
+ component: string;
60
+ /** Results for each variant */
61
+ results: A11yVariantResult[];
62
+ /** Overall status */
63
+ status: 'PASS' | 'WARN' | 'FAIL';
64
+ /** Total violations across all variants */
65
+ totalViolations: number;
66
+ /** Total critical violations */
67
+ totalCritical: number;
68
+ /** Total serious violations */
69
+ totalSerious: number;
70
+ }
71
+
72
+ /**
73
+ * Summary of a11y results
74
+ */
75
+ export interface A11ySummary {
76
+ /** Total number of components */
77
+ totalComponents: number;
78
+ /** Number of accessible components (no critical/serious) */
79
+ accessibleComponents: number;
80
+ /** Percentage of accessible components */
81
+ accessiblePercent: number;
82
+ /** All component results */
83
+ components: A11yComponentResult[];
84
+ /** Total violations across all components */
85
+ totalViolations: number;
86
+ /** Total critical violations */
87
+ totalCritical: number;
88
+ /** Total serious violations */
89
+ totalSerious: number;
90
+ /** Total moderate violations */
91
+ totalModerate: number;
92
+ /** Total minor violations */
93
+ totalMinor: number;
94
+ /** Whether CI check passed (no critical/serious) */
95
+ passed: boolean;
96
+ }
97
+
98
+ /**
99
+ * Run the a11y command
100
+ */
101
+ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
102
+ const { config: configPath, json = false, ci = false, component, port = 6006 } = options;
103
+
104
+ // Load config
105
+ await loadConfig(configPath);
106
+
107
+ const client = createDevServerClient(port);
108
+ const componentResults: A11yComponentResult[] = [];
109
+
110
+ if (!json) {
111
+ console.log(pc.cyan(`\n${BRAND.name} Accessibility Report\n`));
112
+ }
113
+
114
+ // Check if dev server is reachable
115
+ const isReachable = await client.ping();
116
+ if (!isReachable) {
117
+ throw new DevServerConnectionError(
118
+ `Cannot connect to dev server at http://localhost:${port}`,
119
+ `http://localhost:${port}`
120
+ );
121
+ }
122
+
123
+ // Fetch all segments
124
+ const segments = await client.getSegments();
125
+
126
+ if (segments.length === 0) {
127
+ if (json) {
128
+ console.log(JSON.stringify({ error: 'No fragments found', components: [] }));
129
+ } else {
130
+ console.log(pc.yellow('No fragments found.\n'));
131
+ }
132
+ return {
133
+ totalComponents: 0,
134
+ accessibleComponents: 0,
135
+ accessiblePercent: 100,
136
+ components: [],
137
+ totalViolations: 0,
138
+ totalCritical: 0,
139
+ totalSerious: 0,
140
+ totalModerate: 0,
141
+ totalMinor: 0,
142
+ passed: true,
143
+ };
144
+ }
145
+
146
+ // Filter to specific component if requested
147
+ const componentsToCheck = component
148
+ ? segments.filter(s => s.name.toLowerCase() === component.toLowerCase())
149
+ : segments;
150
+
151
+ if (component && componentsToCheck.length === 0) {
152
+ const error = `Component '${component}' not found. Available: ${segments.map(s => s.name).join(', ')}`;
153
+ if (json) {
154
+ console.log(JSON.stringify({ error }));
155
+ } else {
156
+ console.log(pc.red(error));
157
+ }
158
+ throw new Error(error);
159
+ }
160
+
161
+ if (!json) {
162
+ console.log(pc.dim(`Checking ${componentsToCheck.length} component(s) for accessibility issues...\n`));
163
+ }
164
+
165
+ // Check each component
166
+ for (const seg of componentsToCheck) {
167
+ try {
168
+ // Get a11y results from the server
169
+ const a11yResult = await client.getA11y(seg.name);
170
+
171
+ let totalViolations = 0;
172
+ let totalCritical = 0;
173
+ let totalSerious = 0;
174
+
175
+ for (const result of a11yResult.results) {
176
+ totalViolations += result.summary.total;
177
+ totalCritical += result.summary.critical;
178
+ totalSerious += result.summary.serious;
179
+ }
180
+
181
+ // Determine status
182
+ let status: 'PASS' | 'WARN' | 'FAIL' = 'PASS';
183
+ if (totalCritical > 0 || totalSerious > 0) {
184
+ status = 'FAIL';
185
+ } else if (totalViolations > 0) {
186
+ status = 'WARN';
187
+ }
188
+
189
+ componentResults.push({
190
+ component: seg.name,
191
+ results: a11yResult.results,
192
+ status,
193
+ totalViolations,
194
+ totalCritical,
195
+ totalSerious,
196
+ });
197
+ } catch (error) {
198
+ // Handle individual component errors
199
+ componentResults.push({
200
+ component: seg.name,
201
+ results: [],
202
+ status: 'FAIL',
203
+ totalViolations: 0,
204
+ totalCritical: 0,
205
+ totalSerious: 0,
206
+ });
207
+ }
208
+ }
209
+
210
+ // Calculate summary
211
+ const accessibleComponents = componentResults.filter(c => c.status !== 'FAIL').length;
212
+ const totalViolations = componentResults.reduce((sum, c) => sum + c.totalViolations, 0);
213
+ const totalCritical = componentResults.reduce((sum, c) => sum + c.totalCritical, 0);
214
+ const totalSerious = componentResults.reduce((sum, c) => sum + c.totalSerious, 0);
215
+
216
+ // Calculate moderate and minor from all results
217
+ let totalModerate = 0;
218
+ let totalMinor = 0;
219
+ for (const comp of componentResults) {
220
+ for (const result of comp.results) {
221
+ totalModerate += result.summary.moderate;
222
+ totalMinor += result.summary.minor;
223
+ }
224
+ }
225
+
226
+ const summary: A11ySummary = {
227
+ totalComponents: componentResults.length,
228
+ accessibleComponents,
229
+ accessiblePercent: componentResults.length > 0
230
+ ? Math.round((accessibleComponents / componentResults.length) * 100)
231
+ : 100,
232
+ components: componentResults,
233
+ totalViolations,
234
+ totalCritical,
235
+ totalSerious,
236
+ totalModerate,
237
+ totalMinor,
238
+ passed: totalCritical === 0 && totalSerious === 0,
239
+ };
240
+
241
+ if (json) {
242
+ // JSON output for CI/automation
243
+ console.log(JSON.stringify(summary, null, 2));
244
+ } else {
245
+ // Table output for humans
246
+ console.log(pc.bold(
247
+ 'Component'.padEnd(20) +
248
+ 'Variants'.padEnd(10) +
249
+ 'Violations'.padEnd(12) +
250
+ 'Critical'.padEnd(10) +
251
+ 'Serious'.padEnd(10) +
252
+ 'Status'
253
+ ));
254
+ console.log(pc.dim('─'.repeat(72)));
255
+
256
+ for (const result of componentResults) {
257
+ const statusColor = result.status === 'PASS'
258
+ ? pc.green
259
+ : result.status === 'WARN'
260
+ ? pc.yellow
261
+ : pc.red;
262
+
263
+ const variantCount = result.results.length || 1;
264
+
265
+ console.log(
266
+ result.component.padEnd(20) +
267
+ String(variantCount).padEnd(10) +
268
+ String(result.totalViolations).padEnd(12) +
269
+ String(result.totalCritical).padEnd(10) +
270
+ String(result.totalSerious).padEnd(10) +
271
+ statusColor(result.status)
272
+ );
273
+ }
274
+
275
+ console.log(pc.dim('─'.repeat(72)));
276
+
277
+ console.log();
278
+ console.log(pc.bold('Summary:'));
279
+ console.log(` ${accessibleComponents}/${componentResults.length} components accessible (${summary.accessiblePercent}%)`);
280
+ console.log(` Total violations: ${totalViolations} (${totalCritical} critical, ${totalSerious} serious, ${totalModerate} moderate, ${totalMinor} minor)`);
281
+
282
+ if (!summary.passed) {
283
+ console.log();
284
+ console.log(pc.red('✗ Accessibility check failed - critical/serious violations found'));
285
+ } else if (totalViolations > 0) {
286
+ console.log();
287
+ console.log(pc.yellow('⚠ Minor/moderate violations found - consider fixing for better accessibility'));
288
+ } else {
289
+ console.log();
290
+ console.log(pc.green('✓ All components pass accessibility checks'));
291
+ }
292
+
293
+ console.log();
294
+ }
295
+
296
+ // In CI mode, throw if there are critical/serious violations
297
+ if (ci && !summary.passed) {
298
+ throw new Error(`Accessibility check failed: ${totalCritical} critical, ${totalSerious} serious violations found`);
299
+ }
300
+
301
+ return summary;
302
+ }