@fragments-sdk/cli 0.5.2 → 0.6.0

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 (118) hide show
  1. package/dist/bin.js +712 -39
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
  4. package/dist/chunk-6JBGU74P.js.map +1 -0
  5. package/dist/{chunk-U4GQ2JTD.js → chunk-D35RGPAG.js} +412 -35
  6. package/dist/chunk-D35RGPAG.js.map +1 -0
  7. package/dist/{chunk-XNWDI6UT.js → chunk-F7ITZPDJ.js} +5 -5
  8. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  9. package/dist/{chunk-V7YLRR4C.js → chunk-Q7GOHVOK.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-2H2JAA3U.js → chunk-SSLQXHNX.js} +3 -3
  12. package/dist/{core-DKHB7FYV.js → core-SKRPJQZG.js} +4 -4
  13. package/dist/{generate-KL24VZVD.js → generate-7AF7WRVK.js} +5 -5
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +15 -7
  16. package/dist/index.js.map +1 -1
  17. package/dist/{init-NION5S3M.js → init-WKGDPYI4.js} +5 -5
  18. package/dist/mcp-bin.js +8 -220
  19. package/dist/mcp-bin.js.map +1 -1
  20. package/dist/scan-K6JNMCGM.js +12 -0
  21. package/dist/{service-RWUMZ3EW.js → service-F3E4JJM7.js} +5 -5
  22. package/dist/static-viewer-4LQZ5AGA.js +12 -0
  23. package/dist/{test-ECPEXFDN.js → test-CJDNJTPZ.js} +4 -4
  24. package/dist/{tokens-ITADYVPF.js → tokens-JAJABYXP.js} +6 -6
  25. package/dist/viewer-R3Q6WAMJ.js +1822 -0
  26. package/dist/viewer-R3Q6WAMJ.js.map +1 -0
  27. package/package.json +5 -4
  28. package/src/bin.ts +8 -0
  29. package/src/build.ts +104 -13
  30. package/src/cli-commands.ts +18 -0
  31. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  32. package/src/commands/a11y-report.ts +625 -0
  33. package/src/commands/a11y.ts +168 -14
  34. package/src/commands/build.ts +16 -0
  35. package/src/core/auto-props.ts +464 -0
  36. package/src/core/schema.ts +2 -0
  37. package/src/core/types.ts +3 -1
  38. package/src/index.ts +4 -0
  39. package/src/mcp/server.ts +13 -220
  40. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  41. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  42. package/src/theme/contrast.test.ts +331 -0
  43. package/src/theme/contrast.ts +246 -0
  44. package/src/theme/generator.ts +213 -1
  45. package/src/theme/index.ts +16 -0
  46. package/src/theme/types.ts +51 -0
  47. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  48. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  49. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  50. package/src/viewer/components/ActionCapture.tsx +1 -1
  51. package/src/viewer/components/ActionsPanel.tsx +142 -183
  52. package/src/viewer/components/App.tsx +159 -164
  53. package/src/viewer/components/BottomPanel.tsx +40 -80
  54. package/src/viewer/components/CodePanel.tsx +9 -87
  55. package/src/viewer/components/CommandPalette.tsx +117 -74
  56. package/src/viewer/components/ComponentGraph.tsx +143 -126
  57. package/src/viewer/components/ComponentHeader.tsx +46 -43
  58. package/src/viewer/components/ContractPanel.tsx +124 -117
  59. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  60. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  61. package/src/viewer/components/FragmentEditor.tsx +126 -63
  62. package/src/viewer/components/HealthDashboard.tsx +146 -171
  63. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  64. package/src/viewer/components/Icons.tsx +99 -98
  65. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  66. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  67. package/src/viewer/components/IsolatedRender.tsx +12 -6
  68. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  69. package/src/viewer/components/LandingPage.tsx +285 -305
  70. package/src/viewer/components/Layout.tsx +7 -9
  71. package/src/viewer/components/LeftSidebar.tsx +78 -108
  72. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  73. package/src/viewer/components/PreviewArea.tsx +113 -44
  74. package/src/viewer/components/PreviewFrameHost.tsx +6 -5
  75. package/src/viewer/components/PreviewPane.tsx +2 -3
  76. package/src/viewer/components/PreviewToolbar.tsx +61 -104
  77. package/src/viewer/components/PropsEditor.tsx +154 -74
  78. package/src/viewer/components/PropsTable.tsx +95 -82
  79. package/src/viewer/components/RelationsSection.tsx +71 -40
  80. package/src/viewer/components/ResizablePanel.tsx +158 -55
  81. package/src/viewer/components/RightSidebar.tsx +46 -56
  82. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  83. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  84. package/src/viewer/components/StoryRenderer.tsx +4 -11
  85. package/src/viewer/components/Toast.tsx +3 -67
  86. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  87. package/src/viewer/components/UsageSection.tsx +26 -26
  88. package/src/viewer/components/VariantMatrix.tsx +140 -47
  89. package/src/viewer/components/VariantTabs.tsx +24 -68
  90. package/src/viewer/components/ViewportSelector.tsx +106 -110
  91. package/src/viewer/constants/ui.ts +19 -18
  92. package/src/viewer/entry.tsx +8 -3
  93. package/src/viewer/index.ts +3 -6
  94. package/src/viewer/preview-frame.html +21 -5
  95. package/src/viewer/server.ts +7 -16
  96. package/src/viewer/styles/globals.css +4 -4
  97. package/src/viewer/utils/a11y-fixes.ts +53 -30
  98. package/dist/chunk-ICAIQ57V.js.map +0 -1
  99. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  100. package/dist/scan-ESEXV7LF.js +0 -12
  101. package/dist/static-viewer-O37MJ5B6.js +0 -12
  102. package/dist/viewer-YDGFDTK5.js +0 -11104
  103. package/dist/viewer-YDGFDTK5.js.map +0 -1
  104. package/src/viewer/postcss.config.js +0 -6
  105. package/src/viewer/tailwind.config.js +0 -37
  106. /package/dist/{chunk-XNWDI6UT.js.map → chunk-F7ITZPDJ.js.map} +0 -0
  107. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  108. /package/dist/{chunk-V7YLRR4C.js.map → chunk-Q7GOHVOK.js.map} +0 -0
  109. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  110. /package/dist/{chunk-2H2JAA3U.js.map → chunk-SSLQXHNX.js.map} +0 -0
  111. /package/dist/{core-DKHB7FYV.js.map → core-SKRPJQZG.js.map} +0 -0
  112. /package/dist/{generate-KL24VZVD.js.map → generate-7AF7WRVK.js.map} +0 -0
  113. /package/dist/{init-NION5S3M.js.map → init-WKGDPYI4.js.map} +0 -0
  114. /package/dist/{scan-ESEXV7LF.js.map → scan-K6JNMCGM.js.map} +0 -0
  115. /package/dist/{service-RWUMZ3EW.js.map → service-F3E4JJM7.js.map} +0 -0
  116. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-4LQZ5AGA.js.map} +0 -0
  117. /package/dist/{test-ECPEXFDN.js.map → test-CJDNJTPZ.js.map} +0 -0
  118. /package/dist/{tokens-ITADYVPF.js.map → tokens-JAJABYXP.js.map} +0 -0
@@ -5,6 +5,7 @@
5
5
  * for all components in the design system.
6
6
  */
7
7
 
8
+ import fs from 'node:fs';
8
9
  import pc from 'picocolors';
9
10
  import { BRAND } from '../core/index.js';
10
11
  import { loadConfig } from '../core/node.js';
@@ -12,6 +13,7 @@ import {
12
13
  createDevServerClient,
13
14
  DevServerConnectionError,
14
15
  } from '../shared/index.js';
16
+ import { generateA11yReport } from './a11y-report.js';
15
17
 
16
18
  /**
17
19
  * Options for a11y command
@@ -27,6 +29,14 @@ export interface A11yOptions {
27
29
  component?: string;
28
30
  /** Dev server port */
29
31
  port?: number | string;
32
+ /** Output format: table (default), json, or github (markdown) */
33
+ format?: 'table' | 'json' | 'github';
34
+ /** WCAG standard level to check against */
35
+ standard?: 'AA' | 'AAA';
36
+ /** Generate standalone HTML compliance report */
37
+ report?: boolean;
38
+ /** Output path for the HTML report */
39
+ output?: string;
30
40
  }
31
41
 
32
42
  /**
@@ -69,6 +79,18 @@ export interface A11yComponentResult {
69
79
  totalSerious: number;
70
80
  }
71
81
 
82
+ /**
83
+ * A11y score breakdown
84
+ */
85
+ export interface A11yScore {
86
+ /** Numeric score 0–100 */
87
+ score: number;
88
+ /** Percentage of components meeting AA */
89
+ aaPercent: number;
90
+ /** Percentage of components meeting AAA */
91
+ aaaPercent: number;
92
+ }
93
+
72
94
  /**
73
95
  * Summary of a11y results
74
96
  */
@@ -93,13 +115,98 @@ export interface A11ySummary {
93
115
  totalMinor: number;
94
116
  /** Whether CI check passed (no critical/serious) */
95
117
  passed: boolean;
118
+ /** Computed a11y score */
119
+ score?: A11yScore;
120
+ }
121
+
122
+ /**
123
+ * Calculate an accessibility score from the summary.
124
+ *
125
+ * Starts at 100 and subtracts per violation:
126
+ * critical: -10, serious: -5, moderate: -2, minor: -1
127
+ */
128
+ export function calculateA11yScore(summary: A11ySummary): A11yScore {
129
+ const deductions =
130
+ summary.totalCritical * 10 +
131
+ summary.totalSerious * 5 +
132
+ summary.totalModerate * 2 +
133
+ summary.totalMinor * 1;
134
+
135
+ const score = Math.max(0, 100 - deductions);
136
+
137
+ // AA = no critical/serious violations
138
+ const aaComponents = summary.components.filter(
139
+ c => c.totalCritical === 0 && c.totalSerious === 0
140
+ ).length;
141
+ const aaPercent = summary.totalComponents > 0
142
+ ? Math.round((aaComponents / summary.totalComponents) * 100)
143
+ : 100;
144
+
145
+ // AAA = no violations at all
146
+ const aaaComponents = summary.components.filter(
147
+ c => c.totalViolations === 0
148
+ ).length;
149
+ const aaaPercent = summary.totalComponents > 0
150
+ ? Math.round((aaaComponents / summary.totalComponents) * 100)
151
+ : 100;
152
+
153
+ return { score, aaPercent, aaaPercent };
154
+ }
155
+
156
+ /**
157
+ * Format summary as GitHub-flavored Markdown.
158
+ */
159
+ export function formatGitHub(summary: A11ySummary): string {
160
+ const score = summary.score ?? calculateA11yScore(summary);
161
+ const badge = summary.passed ? 'passing' : 'failing';
162
+ const badgeColor = summary.passed ? 'brightgreen' : 'red';
163
+ const lines: string[] = [];
164
+
165
+ lines.push(`## ${BRAND.name} Accessibility Report`);
166
+ lines.push('');
167
+ lines.push(`![a11y](https://img.shields.io/badge/a11y-${badge}-${badgeColor})`);
168
+ lines.push(`**Score:** ${score.score}/100 | **AA:** ${score.aaPercent}% | **AAA:** ${score.aaaPercent}%`);
169
+ lines.push('');
170
+
171
+ // Component table
172
+ lines.push('| Component | Variants | Violations | Critical | Serious | Status |');
173
+ lines.push('|-----------|----------|------------|----------|---------|--------|');
174
+
175
+ for (const result of summary.components) {
176
+ const statusIcon = result.status === 'PASS' ? 'PASS' : result.status === 'WARN' ? 'WARN' : 'FAIL';
177
+ const variantCount = result.results.length || 1;
178
+ lines.push(
179
+ `| ${result.component} | ${variantCount} | ${result.totalViolations} | ${result.totalCritical} | ${result.totalSerious} | ${statusIcon} |`
180
+ );
181
+ }
182
+
183
+ lines.push('');
184
+ lines.push(`**Summary:** ${summary.accessibleComponents}/${summary.totalComponents} components accessible (${summary.accessiblePercent}%)`);
185
+ lines.push(`**Violations:** ${summary.totalViolations} total (${summary.totalCritical} critical, ${summary.totalSerious} serious, ${summary.totalModerate} moderate, ${summary.totalMinor} minor)`);
186
+
187
+ if (summary.totalCritical + summary.totalSerious > 0) {
188
+ lines.push('');
189
+ lines.push(`**Blocking:** ${summary.totalCritical + summary.totalSerious} critical/serious issues must be fixed`);
190
+ }
191
+
192
+ lines.push('');
193
+
194
+ return lines.join('\n');
96
195
  }
97
196
 
98
197
  /**
99
198
  * Run the a11y command
100
199
  */
101
200
  export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
102
- const { config: configPath, json = false, ci = false, component, port = 6006 } = options;
201
+ const {
202
+ config: configPath,
203
+ json = false,
204
+ ci = false,
205
+ component,
206
+ port = 6006,
207
+ format = json ? 'json' : 'table',
208
+ standard = 'AA',
209
+ } = options;
103
210
 
104
211
  // Load config
105
212
  await loadConfig(configPath);
@@ -107,7 +214,9 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
107
214
  const client = createDevServerClient(port);
108
215
  const componentResults: A11yComponentResult[] = [];
109
216
 
110
- if (!json) {
217
+ const isJsonOutput = format === 'json' || json;
218
+
219
+ if (!isJsonOutput && format !== 'github') {
111
220
  console.log(pc.cyan(`\n${BRAND.name} Accessibility Report\n`));
112
221
  }
113
222
 
@@ -124,7 +233,7 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
124
233
  const segments = await client.getSegments();
125
234
 
126
235
  if (segments.length === 0) {
127
- if (json) {
236
+ if (isJsonOutput) {
128
237
  console.log(JSON.stringify({ error: 'No fragments found', components: [] }));
129
238
  } else {
130
239
  console.log(pc.yellow('No fragments found.\n'));
@@ -150,7 +259,7 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
150
259
 
151
260
  if (component && componentsToCheck.length === 0) {
152
261
  const error = `Component '${component}' not found. Available: ${segments.map(s => s.name).join(', ')}`;
153
- if (json) {
262
+ if (isJsonOutput) {
154
263
  console.log(JSON.stringify({ error }));
155
264
  } else {
156
265
  console.log(pc.red(error));
@@ -158,7 +267,7 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
158
267
  throw new Error(error);
159
268
  }
160
269
 
161
- if (!json) {
270
+ if (!isJsonOutput && format !== 'github') {
162
271
  console.log(pc.dim(`Checking ${componentsToCheck.length} component(s) for accessibility issues...\n`));
163
272
  }
164
273
 
@@ -186,6 +295,11 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
186
295
  status = 'WARN';
187
296
  }
188
297
 
298
+ // In AAA mode, any violation is a failure
299
+ if (standard === 'AAA' && totalViolations > 0) {
300
+ status = 'FAIL';
301
+ }
302
+
189
303
  componentResults.push({
190
304
  component: seg.name,
191
305
  results: a11yResult.results,
@@ -194,7 +308,7 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
194
308
  totalCritical,
195
309
  totalSerious,
196
310
  });
197
- } catch (error) {
311
+ } catch {
198
312
  // Handle individual component errors
199
313
  componentResults.push({
200
314
  component: seg.name,
@@ -223,6 +337,10 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
223
337
  }
224
338
  }
225
339
 
340
+ const passed = standard === 'AAA'
341
+ ? totalViolations === 0
342
+ : totalCritical === 0 && totalSerious === 0;
343
+
226
344
  const summary: A11ySummary = {
227
345
  totalComponents: componentResults.length,
228
346
  accessibleComponents,
@@ -235,14 +353,18 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
235
353
  totalSerious,
236
354
  totalModerate,
237
355
  totalMinor,
238
- passed: totalCritical === 0 && totalSerious === 0,
356
+ passed,
239
357
  };
240
358
 
241
- if (json) {
242
- // JSON output for CI/automation
359
+ summary.score = calculateA11yScore(summary);
360
+
361
+ // --- Output ---
362
+ if (format === 'github') {
363
+ console.log(formatGitHub(summary));
364
+ } else if (isJsonOutput) {
243
365
  console.log(JSON.stringify(summary, null, 2));
244
366
  } else {
245
- // Table output for humans
367
+ // Rich table output
246
368
  console.log(pc.bold(
247
369
  'Component'.padEnd(20) +
248
370
  'Variants'.padEnd(10) +
@@ -273,6 +395,30 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
273
395
  }
274
396
 
275
397
  console.log(pc.dim('─'.repeat(72)));
398
+ console.log();
399
+
400
+ // Violation breakdown with dotted leaders
401
+ const categories = [
402
+ { label: 'Critical', count: totalCritical, color: pc.red },
403
+ { label: 'Serious', count: totalSerious, color: pc.red },
404
+ { label: 'Moderate', count: totalModerate, color: pc.yellow },
405
+ { label: 'Minor', count: totalMinor, color: pc.dim },
406
+ ];
407
+
408
+ for (const cat of categories) {
409
+ if (cat.count > 0) {
410
+ const dots = '.'.repeat(Math.max(1, 30 - cat.label.length));
411
+ console.log(` ${cat.label} ${pc.dim(dots)} ${cat.color(String(cat.count))}`);
412
+ }
413
+ }
414
+
415
+ // Score box
416
+ const { score, aaPercent, aaaPercent } = summary.score;
417
+ console.log();
418
+ console.log(pc.bold(` Score: ${score}/100`));
419
+ console.log(` AA compliance .... ${aaPercent}%`);
420
+ console.log(` AAA compliance ... ${aaaPercent}%`);
421
+ console.log(` Standard ......... WCAG ${standard}`);
276
422
 
277
423
  console.log();
278
424
  console.log(pc.bold('Summary:'));
@@ -281,19 +427,27 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
281
427
 
282
428
  if (!summary.passed) {
283
429
  console.log();
284
- console.log(pc.red(' Accessibility check failed - critical/serious violations found'));
430
+ console.log(pc.red('x Accessibility check failed - critical/serious violations found'));
285
431
  } else if (totalViolations > 0) {
286
432
  console.log();
287
- console.log(pc.yellow(' Minor/moderate violations found - consider fixing for better accessibility'));
433
+ console.log(pc.yellow('! Minor/moderate violations found - consider fixing for better accessibility'));
288
434
  } else {
289
435
  console.log();
290
- console.log(pc.green(' All components pass accessibility checks'));
436
+ console.log(pc.green('v All components pass accessibility checks'));
291
437
  }
292
438
 
293
439
  console.log();
294
440
  }
295
441
 
296
- // In CI mode, throw if there are critical/serious violations
442
+ // Generate HTML report if requested
443
+ if (options.report) {
444
+ const outputPath = options.output ?? 'a11y-report.html';
445
+ const html = generateA11yReport(summary);
446
+ fs.writeFileSync(outputPath, html, 'utf-8');
447
+ console.log(pc.green('v Report generated: ' + outputPath));
448
+ }
449
+
450
+ // In CI mode, throw if check did not pass
297
451
  if (ci && !summary.passed) {
298
452
  throw new Error(`Accessibility check failed: ${totalCritical} critical, ${totalSerious} serious violations found`);
299
453
  }
@@ -101,6 +101,14 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
101
101
  console.log();
102
102
  }
103
103
 
104
+ if (result.warnings.length > 0) {
105
+ console.log(pc.yellow('Build warnings:\n'));
106
+ for (const warning of result.warnings) {
107
+ console.log(` ${pc.yellow('⚠')} ${warning.file}: ${warning.warning}`);
108
+ }
109
+ console.log();
110
+ }
111
+
104
112
  segmentCount = result.segmentCount;
105
113
  outputPath = result.outputPath;
106
114
 
@@ -123,6 +131,14 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
123
131
  console.log();
124
132
  }
125
133
 
134
+ if (fragmentsResult.warnings.length > 0) {
135
+ console.log(pc.yellow('Registry warnings:\n'));
136
+ for (const warning of fragmentsResult.warnings) {
137
+ console.log(` ${pc.yellow('⚠')} ${warning.file}: ${warning.warning}`);
138
+ }
139
+ console.log();
140
+ }
141
+
126
142
  componentCount = fragmentsResult.componentCount;
127
143
  registryPath = fragmentsResult.registryPath;
128
144
  contextPath = fragmentsResult.contextPath;