@fragments-sdk/cli 0.10.1 → 0.12.1

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 (223) hide show
  1. package/dist/ai-client-I6MDWNYA.js +21 -0
  2. package/dist/bin.js +292 -367
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
  5. package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
  6. package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
  7. package/dist/chunk-GVDSFQ4E.js.map +1 -0
  8. package/dist/chunk-JJ2VRTBU.js +626 -0
  9. package/dist/chunk-JJ2VRTBU.js.map +1 -0
  10. package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
  11. package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
  12. package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
  13. package/dist/chunk-OQKMEFOS.js.map +1 -0
  14. package/dist/chunk-SXTKFDCR.js +104 -0
  15. package/dist/chunk-SXTKFDCR.js.map +1 -0
  16. package/dist/chunk-T5OMVL7E.js +443 -0
  17. package/dist/chunk-T5OMVL7E.js.map +1 -0
  18. package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
  19. package/dist/chunk-TPWGL2XS.js.map +1 -0
  20. package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
  21. package/dist/chunk-WFS63PCW.js.map +1 -0
  22. package/dist/core/index.js +9 -1
  23. package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
  24. package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
  25. package/dist/index.js +7 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/init-ZSX3NRCZ.js +636 -0
  28. package/dist/init-ZSX3NRCZ.js.map +1 -0
  29. package/dist/mcp-bin.js +2 -2
  30. package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
  31. package/dist/scan-generate-SYU4PYZD.js +1115 -0
  32. package/dist/scan-generate-SYU4PYZD.js.map +1 -0
  33. package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
  34. package/dist/snapshot-XOISO2IS.js +139 -0
  35. package/dist/snapshot-XOISO2IS.js.map +1 -0
  36. package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
  37. package/dist/static-viewer-5GXH2MGE.js.map +1 -0
  38. package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
  39. package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
  40. package/dist/{viewer-DNMNC5VS.js → viewer-7ZEAFBVN.js} +80 -58
  41. package/dist/viewer-7ZEAFBVN.js.map +1 -0
  42. package/package.json +6 -14
  43. package/src/ai-client.ts +156 -0
  44. package/src/bin.ts +74 -2
  45. package/src/build.ts +95 -33
  46. package/src/commands/__tests__/drift-sync.test.ts +252 -0
  47. package/src/commands/__tests__/scan-generate.test.ts +497 -45
  48. package/src/commands/enhance.ts +11 -35
  49. package/src/commands/init.ts +296 -193
  50. package/src/commands/scan-generate.ts +740 -139
  51. package/src/commands/scan.ts +37 -32
  52. package/src/commands/setup.ts +143 -52
  53. package/src/commands/snapshot.ts +197 -0
  54. package/src/commands/sync.ts +357 -0
  55. package/src/commands/validate.ts +43 -1
  56. package/src/core/component-extractor.test.ts +282 -0
  57. package/src/core/component-extractor.ts +1030 -0
  58. package/src/core/discovery.ts +93 -7
  59. package/src/service/enhance/props-extractor.ts +235 -13
  60. package/src/validators.ts +236 -0
  61. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  62. package/src/viewer/server.ts +37 -22
  63. package/src/viewer/vite-plugin.ts +25 -9
  64. package/dist/chunk-5G3VZH43.js.map +0 -1
  65. package/dist/chunk-OQO55NKV.js.map +0 -1
  66. package/dist/chunk-WXSR2II7.js.map +0 -1
  67. package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
  68. package/dist/init-NDQXUWDU.js +0 -796
  69. package/dist/init-NDQXUWDU.js.map +0 -1
  70. package/dist/scan-generate-SJAN5MVI.js +0 -691
  71. package/dist/scan-generate-SJAN5MVI.js.map +0 -1
  72. package/dist/viewer-DNMNC5VS.js.map +0 -1
  73. package/src/ai.ts +0 -266
  74. package/src/commands/init-framework.ts +0 -414
  75. package/src/mcp/bin.ts +0 -36
  76. package/src/migrate/bin.ts +0 -114
  77. package/src/theme/index.ts +0 -77
  78. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  79. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  80. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  81. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  82. package/src/viewer/assets/fragments-logo.ts +0 -4
  83. package/src/viewer/assets/fragments_logo.png +0 -0
  84. package/src/viewer/bin.ts +0 -86
  85. package/src/viewer/cli/health.ts +0 -256
  86. package/src/viewer/cli/index.ts +0 -33
  87. package/src/viewer/cli/scan.ts +0 -124
  88. package/src/viewer/cli/utils.ts +0 -174
  89. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  90. package/src/viewer/components/ActionCapture.tsx +0 -172
  91. package/src/viewer/components/ActionsPanel.tsx +0 -332
  92. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  93. package/src/viewer/components/App.tsx +0 -582
  94. package/src/viewer/components/BottomPanel.tsx +0 -288
  95. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  96. package/src/viewer/components/CodePanel.tsx +0 -118
  97. package/src/viewer/components/CommandPalette.tsx +0 -392
  98. package/src/viewer/components/ComponentDocView.tsx +0 -164
  99. package/src/viewer/components/ComponentGraph.tsx +0 -380
  100. package/src/viewer/components/ComponentHeader.tsx +0 -88
  101. package/src/viewer/components/ContractPanel.tsx +0 -241
  102. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  103. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  104. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  105. package/src/viewer/components/FragmentEditor.tsx +0 -525
  106. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  107. package/src/viewer/components/HeaderSearch.tsx +0 -24
  108. package/src/viewer/components/HealthDashboard.tsx +0 -441
  109. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  110. package/src/viewer/components/Icons.tsx +0 -479
  111. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  112. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  113. package/src/viewer/components/IsolatedRender.tsx +0 -113
  114. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  115. package/src/viewer/components/LandingPage.tsx +0 -421
  116. package/src/viewer/components/Layout.tsx +0 -27
  117. package/src/viewer/components/LeftSidebar.tsx +0 -472
  118. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  119. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  120. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  121. package/src/viewer/components/PanelShell.tsx +0 -161
  122. package/src/viewer/components/PerformancePanel.tsx +0 -304
  123. package/src/viewer/components/PreviewArea.tsx +0 -472
  124. package/src/viewer/components/PreviewAside.tsx +0 -168
  125. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  126. package/src/viewer/components/PreviewPane.tsx +0 -149
  127. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  128. package/src/viewer/components/PropsEditor.tsx +0 -506
  129. package/src/viewer/components/PropsTable.tsx +0 -111
  130. package/src/viewer/components/RelationsSection.tsx +0 -88
  131. package/src/viewer/components/ResizablePanel.tsx +0 -271
  132. package/src/viewer/components/RightSidebar.tsx +0 -102
  133. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  134. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  135. package/src/viewer/components/Sidebar.tsx +0 -169
  136. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  137. package/src/viewer/components/ThemeProvider.tsx +0 -42
  138. package/src/viewer/components/Toast.tsx +0 -3
  139. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  140. package/src/viewer/components/TopToolbar.tsx +0 -159
  141. package/src/viewer/components/UsageSection.tsx +0 -95
  142. package/src/viewer/components/VariantMatrix.tsx +0 -388
  143. package/src/viewer/components/VariantRenderer.tsx +0 -131
  144. package/src/viewer/components/VariantTabs.tsx +0 -40
  145. package/src/viewer/components/ViewerHeader.tsx +0 -69
  146. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  147. package/src/viewer/components/ViewportSelector.tsx +0 -172
  148. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  149. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  150. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  151. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  152. package/src/viewer/components/viewer-utils.ts +0 -16
  153. package/src/viewer/composition-renderer.ts +0 -381
  154. package/src/viewer/constants/index.ts +0 -1
  155. package/src/viewer/constants/ui.ts +0 -166
  156. package/src/viewer/entry.tsx +0 -335
  157. package/src/viewer/hooks/index.ts +0 -2
  158. package/src/viewer/hooks/useA11yCache.ts +0 -383
  159. package/src/viewer/hooks/useA11yService.ts +0 -364
  160. package/src/viewer/hooks/useActions.ts +0 -138
  161. package/src/viewer/hooks/useAppState.ts +0 -147
  162. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  163. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  164. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  165. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  166. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  167. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  168. package/src/viewer/hooks/useUrlState.ts +0 -318
  169. package/src/viewer/hooks/useViewSettings.ts +0 -111
  170. package/src/viewer/index.html +0 -28
  171. package/src/viewer/intelligence/healthReport.ts +0 -505
  172. package/src/viewer/intelligence/styleDrift.ts +0 -340
  173. package/src/viewer/intelligence/usageScanner.ts +0 -309
  174. package/src/viewer/jsx-parser.ts +0 -486
  175. package/src/viewer/preview-frame-entry.tsx +0 -25
  176. package/src/viewer/preview-frame.html +0 -125
  177. package/src/viewer/public/favicon.ico +0 -0
  178. package/src/viewer/render-template.html +0 -68
  179. package/src/viewer/styles/globals.css +0 -278
  180. package/src/viewer/types/a11y.ts +0 -197
  181. package/src/viewer/utils/a11y-fixes.ts +0 -509
  182. package/src/viewer/utils/actionExport.ts +0 -372
  183. package/src/viewer/utils/colorSchemes.ts +0 -201
  184. package/src/viewer/utils/detectRelationships.ts +0 -256
  185. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  186. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  187. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  188. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  189. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  190. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  191. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  192. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  193. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  194. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  195. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  196. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  197. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  198. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -137
  199. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  200. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  201. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  202. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  203. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  204. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  205. package/src/viewer/vendor/shared/src/index.ts +0 -34
  206. package/src/viewer/vendor/shared/src/types.ts +0 -53
  207. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  208. package/src/viewer/webmcp/analytics.ts +0 -165
  209. package/src/viewer/webmcp/index.ts +0 -3
  210. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  211. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  212. package/src/viewer/webmcp/scan-utils.ts +0 -135
  213. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  214. package/src/viewer/webmcp/viewer-state.ts +0 -45
  215. /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
  216. /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
  217. /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
  218. /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
  219. /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
  220. /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
  221. /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
  222. /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
  223. /package/dist/{tokens-CE46OTMD.js.map → tokens-T6SIVUT5.js.map} +0 -0
package/src/viewer/bin.ts DELETED
@@ -1,86 +0,0 @@
1
- #!/usr/bin/env node
2
- import pc from 'picocolors';
3
- import { BRAND } from '../core/index.js';
4
- import { createDevServer } from './server.js';
5
- import { runCommand } from './cli/index.js';
6
-
7
- async function startDevServer(): Promise<void> {
8
- const args = process.argv.slice(2);
9
- const portArg = args.find((arg) => arg.startsWith('--port='));
10
- const port = portArg ? parseInt(portArg.split('=')[1], 10) : 6006;
11
- const noOpen = args.includes('--no-open');
12
-
13
- console.log(pc.cyan(`\n${BRAND.name} Dev Server\n`));
14
- console.log(pc.dim('Starting development server...\n'));
15
-
16
- try {
17
- const server = await createDevServer({
18
- port,
19
- open: !noOpen,
20
- });
21
-
22
- const address = server.httpServer?.address();
23
- const actualPort = typeof address === 'object' && address ? address.port : port;
24
-
25
- console.log(pc.green(`✓ Server running at http://localhost:${actualPort}\n`));
26
- console.log(pc.dim('Press Ctrl+C to stop\n'));
27
- } catch (error) {
28
- console.error(pc.red('Failed to start server:'), error);
29
- process.exit(1);
30
- }
31
- }
32
-
33
- function showHelp(): void {
34
- console.log(pc.cyan(`\n${BRAND.name} CLI\n`));
35
- console.log(`Usage: ${BRAND.cliCommand} <command> [options]\n`);
36
- console.log('Commands:');
37
- console.log(' dev Start the development server (default)');
38
- console.log(' scan <dir> Scan codebase for component usage');
39
- console.log(' health Generate design system health report');
40
- console.log('');
41
- console.log('Dev Server Options:');
42
- console.log(' --port=<port> Port to run on (default: 6006)');
43
- console.log(' --no-open Don\'t open browser automatically');
44
- console.log('');
45
- console.log('Scan Options:');
46
- console.log(' --format=<fmt> Output format: table, json (default: table)');
47
- console.log(' --component=<n> Filter to specific component');
48
- console.log(' --verbose Show detailed usage info');
49
- console.log('');
50
- console.log('Health Options:');
51
- console.log(' --format=<fmt> Output format: table, json, markdown (default: table)');
52
- console.log(' --scan=<dir> Include codebase usage scan');
53
- console.log(' --ci CI mode: exit 1 if score below threshold');
54
- console.log(' --threshold=<n> Minimum score for CI mode (default: 80)');
55
- console.log('');
56
- }
57
-
58
- async function main(): Promise<void> {
59
- const args = process.argv.slice(2);
60
- const command = args[0];
61
-
62
- // Show help
63
- if (command === '--help' || command === '-h' || command === 'help') {
64
- showHelp();
65
- return;
66
- }
67
-
68
- // Route to subcommands
69
- if (command === 'scan' || command === 'health') {
70
- await runCommand(command, args.slice(1));
71
- return;
72
- }
73
-
74
- // Default: start dev server (handles 'dev' command or no command)
75
- if (!command || command === 'dev' || command.startsWith('--')) {
76
- await startDevServer();
77
- return;
78
- }
79
-
80
- // Unknown command
81
- console.error(pc.red(`Unknown command: ${command}`));
82
- console.log(pc.dim(`Run "${BRAND.cliCommand} --help" for usage information.\n`));
83
- process.exit(1);
84
- }
85
-
86
- main();
@@ -1,256 +0,0 @@
1
- /**
2
- * Health Command
3
- * Generates a comprehensive design system health report
4
- */
5
-
6
- import pc from 'picocolors';
7
- import fs from 'fs';
8
- import path from 'path';
9
- import { BRAND } from '../../core/index.js';
10
- import { loadConfig } from '../../core/node.js';
11
- import { scanForUsages } from '../intelligence/usageScanner.js';
12
- import { generateHealthReport, formatHealthReportAsMarkdown, type HealthReport } from '../intelligence/healthReport.js';
13
- import {
14
- parseArgs,
15
- createSpinner,
16
- formatScore,
17
- printHeader,
18
- printKV,
19
- printWarning,
20
- printSuccess,
21
- formatList,
22
- } from './utils.js';
23
-
24
- type OutputFormat = 'table' | 'json' | 'markdown';
25
-
26
- interface HealthCommandOptions {
27
- format: OutputFormat;
28
- scanDir?: string;
29
- ci: boolean;
30
- threshold: number;
31
- }
32
-
33
- function parseOptions(args: string[]): HealthCommandOptions {
34
- const { options } = parseArgs(args);
35
-
36
- return {
37
- format: (options.format as OutputFormat) || 'table',
38
- scanDir: options.scan as string | undefined,
39
- ci: !!options.ci,
40
- threshold: options.threshold ? parseInt(options.threshold as string, 10) : 80,
41
- };
42
- }
43
-
44
- /**
45
- * Load fragments from the project
46
- */
47
- async function loadFragments(): Promise<Array<{ path: string; fragment: any }>> {
48
- const configResult = await loadConfig();
49
-
50
- if (!configResult) {
51
- throw new Error('No fragments.config.ts found. Run this command from a Fragments project.');
52
- }
53
-
54
- const { config } = configResult;
55
-
56
- // Try to load compiled fragments
57
- const fragmentsPath = path.resolve(process.cwd(), config.outFile || `${BRAND.dataDir}/${BRAND.outFile}`);
58
-
59
- if (fs.existsSync(fragmentsPath)) {
60
- const data = JSON.parse(fs.readFileSync(fragmentsPath, 'utf-8'));
61
- return Object.entries(data.fragments || {}).map(([name, fragment]: [string, any]) => ({
62
- path: fragment.filePath || name,
63
- fragment: {
64
- meta: fragment.meta || { name, description: '', category: '' },
65
- usage: fragment.usage || { when: [], whenNot: [] },
66
- props: fragment.props || {},
67
- variants: fragment.variants || [],
68
- relations: fragment.relations,
69
- contract: fragment.contract,
70
- _generated: fragment._generated,
71
- component: () => null, // Placeholder
72
- },
73
- }));
74
- }
75
-
76
- // Fallback: return empty if no compiled fragments
77
- console.log(pc.yellow(`No compiled fragments found. Run \`${BRAND.cliCommand} build\` first for complete analysis.`));
78
- return [];
79
- }
80
-
81
- function formatGrade(grade: string): string {
82
- switch (grade) {
83
- case 'A':
84
- return pc.green(pc.bold(grade));
85
- case 'B':
86
- return pc.cyan(grade);
87
- case 'C':
88
- return pc.yellow(grade);
89
- case 'D':
90
- return pc.magenta(grade);
91
- case 'F':
92
- return pc.red(pc.bold(grade));
93
- default:
94
- return grade;
95
- }
96
- }
97
-
98
- function formatTableOutput(report: HealthReport): void {
99
- console.log(pc.cyan(`\n${BRAND.name} Health Report\n`));
100
- console.log(pc.dim(`Generated: ${new Date(report.generatedAt).toLocaleString()}\n`));
101
-
102
- // Overall score
103
- const scoreColor = report.overallScore >= 80 ? pc.green : report.overallScore >= 60 ? pc.yellow : pc.red;
104
- console.log(` ${pc.bold('Overall Score:')} ${scoreColor(pc.bold(`${report.overallScore}%`))}\n`);
105
-
106
- // Grade summary
107
- console.log(pc.bold(' Category Grades:\n'));
108
- console.log(` Usage: ${formatGrade(report.grades.usage)} (${report.usage.adoptionRate}% adoption)`);
109
- console.log(` Style Drift: ${formatGrade(report.grades.drift)} (${report.styleDrift.averageCompliance}% compliance)`);
110
- console.log(` Documentation: ${formatGrade(report.grades.documentation)} (${report.documentation.documentationRate}% documented)`);
111
- console.log(` Token Usage: ${formatGrade(report.grades.tokens)} (${report.tokens.tokenizationRate}% tokenized)`);
112
- console.log('');
113
-
114
- // Usage section
115
- printHeader('Usage Statistics');
116
- printKV('Total Components', report.usage.totalComponents.toString());
117
- printKV('Used Components', report.usage.usedComponents.toString());
118
- printKV('Adoption Rate', formatScore(report.usage.adoptionRate));
119
-
120
- if (report.usage.mostUsed.length > 0) {
121
- console.log(pc.dim('\n Most Used:'));
122
- for (const item of report.usage.mostUsed.slice(0, 5)) {
123
- console.log(` ${pc.cyan(item.component)}: ${item.count} uses`);
124
- }
125
- }
126
-
127
- if (report.usage.unusedComponents.length > 0 && report.usage.unusedComponents.length <= 5) {
128
- console.log(pc.dim('\n Unused:'));
129
- for (const name of report.usage.unusedComponents) {
130
- printWarning(name);
131
- }
132
- } else if (report.usage.unusedComponents.length > 5) {
133
- printWarning(`${report.usage.unusedComponents.length} unused components`);
134
- }
135
-
136
- // Style Drift section
137
- printHeader('Style Drift Analysis');
138
- printKV('Components with Drift', report.styleDrift.componentsWithDrift.toString());
139
- printKV('Total Drifts', report.styleDrift.totalDrifts.toString());
140
- printKV('Average Compliance', formatScore(report.styleDrift.averageCompliance));
141
-
142
- if (report.styleDrift.bySeverity.high > 0) {
143
- console.log('');
144
- printWarning(`${report.styleDrift.bySeverity.high} high-severity drift(s) - affects brand identity`);
145
- }
146
-
147
- if (report.styleDrift.worstOffenders.length > 0) {
148
- console.log(pc.dim('\n Needs Attention:'));
149
- for (const item of report.styleDrift.worstOffenders.slice(0, 3)) {
150
- console.log(` ${pc.red('!')} ${item.component} (${item.variant}): ${item.driftCount} drifts`);
151
- }
152
- }
153
-
154
- // Documentation section
155
- printHeader('Documentation Coverage');
156
- printKV('Documented', `${report.documentation.documented} components`);
157
- printKV('Coverage', formatScore(report.documentation.documentationRate));
158
-
159
- if (report.documentation.missingDescriptions.length > 0) {
160
- printWarning(`${report.documentation.missingDescriptions.length} missing descriptions`);
161
- }
162
-
163
- if (report.documentation.missingUsage.length > 0) {
164
- printWarning(`${report.documentation.missingUsage.length} missing usage guidelines`);
165
- }
166
-
167
- // Token section
168
- printHeader('Design Token Usage');
169
- printKV('Token Compliance', formatScore(report.tokens.complianceScore));
170
- printKV('Tokenized Values', report.tokens.tokenizedValues.toString());
171
- printKV('Hardcoded Values', report.tokens.hardcodedValues.toString(), report.tokens.hardcodedValues > 0 ? pc.yellow : undefined);
172
-
173
- // Recommendations
174
- if (report.recommendations.length > 0) {
175
- printHeader('Recommendations');
176
- console.log('');
177
- for (const rec of report.recommendations) {
178
- console.log(` ${pc.yellow('→')} ${rec}`);
179
- }
180
- }
181
-
182
- console.log('');
183
- }
184
-
185
- function formatJsonOutput(report: HealthReport): void {
186
- console.log(JSON.stringify(report, null, 2));
187
- }
188
-
189
- function formatMarkdownOutput(report: HealthReport): void {
190
- console.log(formatHealthReportAsMarkdown(report));
191
- }
192
-
193
- /**
194
- * Run the health command
195
- */
196
- export async function healthCommand(args: string[]): Promise<void> {
197
- const options = parseOptions(args);
198
-
199
- const spinner = createSpinner('Analyzing design system health...');
200
- spinner.start();
201
-
202
- try {
203
- // Load fragments
204
- spinner.update('Loading fragments...');
205
- const fragments = await loadFragments();
206
-
207
- if (fragments.length === 0) {
208
- spinner.stop(false, 'No fragments found');
209
- console.log(pc.yellow(`\nNo components to analyze. Create fragment files or run \`${BRAND.cliCommand} build\`.\n`));
210
- return;
211
- }
212
-
213
- // Run usage scan if directory provided
214
- let usageScan;
215
- if (options.scanDir) {
216
- spinner.update(`Scanning ${options.scanDir} for component usage...`);
217
- usageScan = await scanForUsages({ directory: options.scanDir });
218
- }
219
-
220
- // Generate report
221
- spinner.update('Generating health report...');
222
- const report = generateHealthReport({
223
- fragments,
224
- usageScan,
225
- // Note: Drift reports would require running the viewer and extracting styles
226
- // For CLI, we focus on what can be statically analyzed
227
- });
228
-
229
- spinner.stop(true, 'Health analysis complete');
230
-
231
- // Output
232
- switch (options.format) {
233
- case 'json':
234
- formatJsonOutput(report);
235
- break;
236
- case 'markdown':
237
- formatMarkdownOutput(report);
238
- break;
239
- default:
240
- formatTableOutput(report);
241
- }
242
-
243
- // CI mode: exit with error if below threshold
244
- if (options.ci) {
245
- if (report.overallScore < options.threshold) {
246
- console.log(pc.red(`\nCI Check Failed: Score ${report.overallScore}% is below threshold ${options.threshold}%\n`));
247
- process.exit(1);
248
- } else {
249
- console.log(pc.green(`\nCI Check Passed: Score ${report.overallScore}% meets threshold ${options.threshold}%\n`));
250
- }
251
- }
252
- } catch (error) {
253
- spinner.stop(false, 'Health analysis failed');
254
- throw error;
255
- }
256
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * CLI Command Router
3
- * Routes subcommands to their respective handlers
4
- */
5
-
6
- import pc from 'picocolors';
7
- import { scanCommand } from './scan.js';
8
- import { healthCommand } from './health.js';
9
-
10
- export type CommandName = 'scan' | 'health';
11
-
12
- /**
13
- * Run a CLI command with the given arguments
14
- */
15
- export async function runCommand(command: CommandName, args: string[]): Promise<void> {
16
- try {
17
- switch (command) {
18
- case 'scan':
19
- await scanCommand(args);
20
- break;
21
- case 'health':
22
- await healthCommand(args);
23
- break;
24
- default:
25
- console.error(pc.red(`Unknown command: ${command}`));
26
- process.exit(1);
27
- }
28
- } catch (error) {
29
- const message = error instanceof Error ? error.message : String(error);
30
- console.error(pc.red(`\nError: ${message}\n`));
31
- process.exit(1);
32
- }
33
- }
@@ -1,124 +0,0 @@
1
- /**
2
- * Scan Command
3
- * Scans a codebase for component usage
4
- */
5
-
6
- import pc from 'picocolors';
7
- import path from 'path';
8
- import { scanForUsages, type FullScanResult, type UsageScanResult } from '../intelligence/usageScanner.js';
9
- import { parseArgs, createSpinner, formatTable, formatDuration, printHeader, printKV } from './utils.js';
10
-
11
- type OutputFormat = 'table' | 'json';
12
-
13
- interface ScanCommandOptions {
14
- format: OutputFormat;
15
- component?: string;
16
- verbose: boolean;
17
- }
18
-
19
- function parseOptions(args: string[]): { directory: string; options: ScanCommandOptions } {
20
- const { positional, options } = parseArgs(args);
21
-
22
- return {
23
- directory: positional[0] || '.',
24
- options: {
25
- format: (options.format as OutputFormat) || 'table',
26
- component: options.component as string | undefined,
27
- verbose: !!options.verbose || !!options.v,
28
- },
29
- };
30
- }
31
-
32
- function formatTableOutput(result: FullScanResult, options: ScanCommandOptions): void {
33
- const { results, summary } = result;
34
-
35
- printHeader('Component Usage Scan');
36
- printKV('Directory scanned', path.resolve(result.results[0]?.usages[0]?.file || '.').split(path.sep).slice(0, -1).join(path.sep) || '.');
37
- printKV('Files scanned', summary.totalFiles.toString());
38
- printKV('Files with usage', summary.filesWithUsage.toString());
39
- printKV('Scan time', formatDuration(summary.scanTimeMs));
40
- console.log('');
41
-
42
- if (results.length === 0) {
43
- console.log(pc.yellow(' No component usages found.\n'));
44
- return;
45
- }
46
-
47
- // Summary table
48
- const headers = ['Component', 'Files', 'Total Uses'];
49
- const rows = results.map((r) => [
50
- r.component,
51
- String(r.usages.length),
52
- String(r.totalUsages),
53
- ]);
54
-
55
- console.log(formatTable(headers, rows));
56
- console.log('');
57
-
58
- // Verbose: show file details
59
- if (options.verbose) {
60
- console.log(pc.bold('\nDetailed Usage:\n'));
61
-
62
- for (const r of results) {
63
- console.log(pc.cyan(` ${r.component}`));
64
- for (const usage of r.usages) {
65
- const relativePath = path.relative(process.cwd(), usage.file);
66
- const importIcon = usage.importType === 'named' ? '{}' : usage.importType === 'default' ? '=>' : '*';
67
- console.log(pc.dim(` ${importIcon} ${relativePath}:${usage.line} (${usage.usageCount} uses)`));
68
- }
69
- console.log('');
70
- }
71
- }
72
-
73
- // Summary footer
74
- console.log(pc.dim('─'.repeat(50)));
75
- console.log(` ${pc.bold(String(summary.totalComponents))} components used ${pc.bold(String(summary.totalUsages))} times across ${pc.bold(String(summary.filesWithUsage))} files`);
76
- console.log('');
77
- }
78
-
79
- function formatJsonOutput(result: FullScanResult): void {
80
- const output = {
81
- summary: result.summary,
82
- components: result.results.map((r) => ({
83
- name: r.component,
84
- totalUsages: r.totalUsages,
85
- fileCount: r.usages.length,
86
- usages: r.usages.map((u) => ({
87
- file: path.relative(process.cwd(), u.file),
88
- line: u.line,
89
- importType: u.importType,
90
- usageCount: u.usageCount,
91
- })),
92
- })),
93
- };
94
-
95
- console.log(JSON.stringify(output, null, 2));
96
- }
97
-
98
- /**
99
- * Run the scan command
100
- */
101
- export async function scanCommand(args: string[]): Promise<void> {
102
- const { directory, options } = parseOptions(args);
103
-
104
- const spinner = createSpinner(`Scanning ${directory} for component usage...`);
105
- spinner.start();
106
-
107
- try {
108
- const result = await scanForUsages({
109
- directory,
110
- components: options.component ? [options.component] : undefined,
111
- });
112
-
113
- spinner.stop(true, `Scanned ${result.summary.totalFiles} files in ${formatDuration(result.summary.scanTimeMs)}`);
114
-
115
- if (options.format === 'json') {
116
- formatJsonOutput(result);
117
- } else {
118
- formatTableOutput(result, options);
119
- }
120
- } catch (error) {
121
- spinner.stop(false, 'Scan failed');
122
- throw error;
123
- }
124
- }
@@ -1,174 +0,0 @@
1
- /**
2
- * Shared CLI Utilities
3
- * Common helpers for CLI commands
4
- */
5
-
6
- import pc from 'picocolors';
7
-
8
- /**
9
- * Parse command line arguments into options object
10
- */
11
- export function parseArgs(args: string[]): {
12
- positional: string[];
13
- options: Record<string, string | boolean>;
14
- } {
15
- const positional: string[] = [];
16
- const options: Record<string, string | boolean> = {};
17
-
18
- for (const arg of args) {
19
- if (arg.startsWith('--')) {
20
- const [key, value] = arg.slice(2).split('=');
21
- options[key] = value ?? true;
22
- } else if (arg.startsWith('-')) {
23
- const key = arg.slice(1);
24
- options[key] = true;
25
- } else {
26
- positional.push(arg);
27
- }
28
- }
29
-
30
- return { positional, options };
31
- }
32
-
33
- /**
34
- * Spinner for async operations
35
- */
36
- export function createSpinner(message: string): {
37
- start: () => void;
38
- stop: (success?: boolean, finalMessage?: string) => void;
39
- update: (message: string) => void;
40
- } {
41
- const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
42
- let frameIndex = 0;
43
- let interval: NodeJS.Timeout | null = null;
44
- let currentMessage = message;
45
-
46
- return {
47
- start() {
48
- process.stdout.write('\n');
49
- interval = setInterval(() => {
50
- process.stdout.write(`\r${pc.cyan(frames[frameIndex])} ${currentMessage}`);
51
- frameIndex = (frameIndex + 1) % frames.length;
52
- }, 80);
53
- },
54
- stop(success = true, finalMessage?: string) {
55
- if (interval) {
56
- clearInterval(interval);
57
- interval = null;
58
- }
59
- const icon = success ? pc.green('✓') : pc.red('✗');
60
- process.stdout.write(`\r${icon} ${finalMessage || currentMessage}\n`);
61
- },
62
- update(message: string) {
63
- currentMessage = message;
64
- },
65
- };
66
- }
67
-
68
- /**
69
- * Format a table for console output
70
- */
71
- export function formatTable(
72
- headers: string[],
73
- rows: string[][],
74
- options: { padding?: number; maxWidth?: number } = {}
75
- ): string {
76
- const { padding = 2, maxWidth = 50 } = options;
77
-
78
- // Calculate column widths
79
- const widths = headers.map((h, i) => {
80
- const columnValues = [h, ...rows.map((r) => r[i] || '')];
81
- return Math.min(maxWidth, Math.max(...columnValues.map((v) => v.length)));
82
- });
83
-
84
- // Format header
85
- const headerLine = headers
86
- .map((h, i) => h.padEnd(widths[i] + padding))
87
- .join('');
88
- const separator = widths.map((w) => '─'.repeat(w + padding)).join('');
89
-
90
- // Format rows
91
- const formattedRows = rows.map((row) =>
92
- row.map((cell, i) => (cell || '').padEnd(widths[i] + padding)).join('')
93
- );
94
-
95
- return [
96
- pc.bold(headerLine),
97
- pc.dim(separator),
98
- ...formattedRows,
99
- ].join('\n');
100
- }
101
-
102
- /**
103
- * Format bytes as human-readable size
104
- */
105
- export function formatBytes(bytes: number): string {
106
- if (bytes === 0) return '0 B';
107
- const k = 1024;
108
- const sizes = ['B', 'KB', 'MB', 'GB'];
109
- const i = Math.floor(Math.log(bytes) / Math.log(k));
110
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
111
- }
112
-
113
- /**
114
- * Format duration in milliseconds
115
- */
116
- export function formatDuration(ms: number): string {
117
- if (ms < 1000) return `${ms}ms`;
118
- if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
119
- return `${(ms / 60000).toFixed(1)}m`;
120
- }
121
-
122
- /**
123
- * Format a score with color based on thresholds
124
- */
125
- export function formatScore(score: number, thresholds = { good: 80, warn: 50 }): string {
126
- const formatted = `${score}%`;
127
- if (score >= thresholds.good) return pc.green(formatted);
128
- if (score >= thresholds.warn) return pc.yellow(formatted);
129
- return pc.red(formatted);
130
- }
131
-
132
- /**
133
- * Print a section header
134
- */
135
- export function printHeader(title: string): void {
136
- console.log(`\n${pc.bold(pc.cyan(title))}`);
137
- console.log(pc.dim('─'.repeat(title.length + 4)));
138
- }
139
-
140
- /**
141
- * Print a key-value pair
142
- */
143
- export function printKV(key: string, value: string | number, color?: (s: string) => string): void {
144
- const valueStr = String(value);
145
- console.log(` ${pc.dim(key + ':')} ${color ? color(valueStr) : valueStr}`);
146
- }
147
-
148
- /**
149
- * Print a warning message
150
- */
151
- export function printWarning(message: string): void {
152
- console.log(pc.yellow(` ⚠ ${message}`));
153
- }
154
-
155
- /**
156
- * Print an error message
157
- */
158
- export function printError(message: string): void {
159
- console.log(pc.red(` ✗ ${message}`));
160
- }
161
-
162
- /**
163
- * Print a success message
164
- */
165
- export function printSuccess(message: string): void {
166
- console.log(pc.green(` ✓ ${message}`));
167
- }
168
-
169
- /**
170
- * Format a list for bullet points
171
- */
172
- export function formatList(items: string[], bullet = '•'): string {
173
- return items.map((item) => ` ${pc.dim(bullet)} ${item}`).join('\n');
174
- }