@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.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics engine for design system insights.
|
|
3
|
+
*
|
|
4
|
+
* Analyzes compiled segments to provide:
|
|
5
|
+
* - Component inventory stats
|
|
6
|
+
* - Documentation coverage
|
|
7
|
+
* - Usage pattern insights
|
|
8
|
+
* - Quality scores
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { CompiledSegmentsFile, CompiledSegment } from "../core/index.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Overall design system analytics
|
|
15
|
+
*/
|
|
16
|
+
export interface DesignSystemAnalytics {
|
|
17
|
+
/** When the analysis was performed */
|
|
18
|
+
analyzedAt: Date;
|
|
19
|
+
|
|
20
|
+
/** Summary metrics */
|
|
21
|
+
summary: {
|
|
22
|
+
totalComponents: number;
|
|
23
|
+
totalVariants: number;
|
|
24
|
+
totalProps: number;
|
|
25
|
+
categories: string[];
|
|
26
|
+
overallScore: number; // 0-100
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Component inventory */
|
|
30
|
+
inventory: ComponentInventory;
|
|
31
|
+
|
|
32
|
+
/** Documentation coverage */
|
|
33
|
+
coverage: CoverageAnalytics;
|
|
34
|
+
|
|
35
|
+
/** Quality insights */
|
|
36
|
+
quality: QualityAnalytics;
|
|
37
|
+
|
|
38
|
+
/** Distribution charts data */
|
|
39
|
+
distribution: DistributionAnalytics;
|
|
40
|
+
|
|
41
|
+
/** Recommendations for improvement */
|
|
42
|
+
recommendations: Recommendation[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Component inventory breakdown
|
|
47
|
+
*/
|
|
48
|
+
export interface ComponentInventory {
|
|
49
|
+
/** Components by category */
|
|
50
|
+
byCategory: Record<string, ComponentSummary[]>;
|
|
51
|
+
|
|
52
|
+
/** Components by status */
|
|
53
|
+
byStatus: Record<string, ComponentSummary[]>;
|
|
54
|
+
|
|
55
|
+
/** Components sorted by variant count (most to least) */
|
|
56
|
+
byVariantCount: ComponentSummary[];
|
|
57
|
+
|
|
58
|
+
/** Components sorted by prop count */
|
|
59
|
+
byPropCount: ComponentSummary[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Summary info for a single component
|
|
64
|
+
*/
|
|
65
|
+
export interface ComponentSummary {
|
|
66
|
+
name: string;
|
|
67
|
+
category: string;
|
|
68
|
+
status: string;
|
|
69
|
+
variantCount: number;
|
|
70
|
+
propCount: number;
|
|
71
|
+
hasUsageWhen: boolean;
|
|
72
|
+
hasUsageWhenNot: boolean;
|
|
73
|
+
hasGuidelines: boolean;
|
|
74
|
+
hasRelations: boolean;
|
|
75
|
+
documentationScore: number; // 0-100
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Documentation coverage analytics
|
|
80
|
+
*/
|
|
81
|
+
export interface CoverageAnalytics {
|
|
82
|
+
/** Overall coverage percentage */
|
|
83
|
+
overall: number;
|
|
84
|
+
|
|
85
|
+
/** Coverage by field */
|
|
86
|
+
fields: {
|
|
87
|
+
description: { covered: number; total: number; percentage: number };
|
|
88
|
+
usageWhen: { covered: number; total: number; percentage: number };
|
|
89
|
+
usageWhenNot: { covered: number; total: number; percentage: number };
|
|
90
|
+
guidelines: { covered: number; total: number; percentage: number };
|
|
91
|
+
accessibility: { covered: number; total: number; percentage: number };
|
|
92
|
+
relations: { covered: number; total: number; percentage: number };
|
|
93
|
+
propDescriptions: { covered: number; total: number; percentage: number };
|
|
94
|
+
propConstraints: { covered: number; total: number; percentage: number };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** Components with incomplete documentation */
|
|
98
|
+
incomplete: Array<{
|
|
99
|
+
component: string;
|
|
100
|
+
missing: string[];
|
|
101
|
+
}>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Quality insights
|
|
106
|
+
*/
|
|
107
|
+
export interface QualityAnalytics {
|
|
108
|
+
/** Components with no whenNot guidance (anti-patterns) */
|
|
109
|
+
missingWhenNot: string[];
|
|
110
|
+
|
|
111
|
+
/** Components with no relations defined */
|
|
112
|
+
isolated: string[];
|
|
113
|
+
|
|
114
|
+
/** Deprecated components still in use */
|
|
115
|
+
deprecated: string[];
|
|
116
|
+
|
|
117
|
+
/** Components with very few variants (might need more examples) */
|
|
118
|
+
fewVariants: string[];
|
|
119
|
+
|
|
120
|
+
/** Props without descriptions */
|
|
121
|
+
undocumentedProps: Array<{ component: string; prop: string }>;
|
|
122
|
+
|
|
123
|
+
/** Props without constraints (might need validation rules) */
|
|
124
|
+
unconstrainedProps: Array<{ component: string; prop: string }>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Distribution data for charts
|
|
129
|
+
*/
|
|
130
|
+
export interface DistributionAnalytics {
|
|
131
|
+
/** Variants per component distribution */
|
|
132
|
+
variantsPerComponent: Array<{ name: string; count: number }>;
|
|
133
|
+
|
|
134
|
+
/** Props per component distribution */
|
|
135
|
+
propsPerComponent: Array<{ name: string; count: number }>;
|
|
136
|
+
|
|
137
|
+
/** Components per category */
|
|
138
|
+
componentsPerCategory: Array<{ category: string; count: number }>;
|
|
139
|
+
|
|
140
|
+
/** Status distribution */
|
|
141
|
+
statusDistribution: Array<{ status: string; count: number }>;
|
|
142
|
+
|
|
143
|
+
/** Tag frequency */
|
|
144
|
+
tagFrequency: Array<{ tag: string; count: number }>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Actionable recommendation
|
|
149
|
+
*/
|
|
150
|
+
export interface Recommendation {
|
|
151
|
+
priority: "high" | "medium" | "low";
|
|
152
|
+
category: "documentation" | "coverage" | "quality" | "organization";
|
|
153
|
+
title: string;
|
|
154
|
+
description: string;
|
|
155
|
+
components?: string[];
|
|
156
|
+
impact: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Analyze a compiled segments file and produce analytics
|
|
161
|
+
*/
|
|
162
|
+
export function analyzeDesignSystem(
|
|
163
|
+
data: CompiledSegmentsFile
|
|
164
|
+
): DesignSystemAnalytics {
|
|
165
|
+
const segments = Object.values(data.segments);
|
|
166
|
+
const analyzedAt = new Date();
|
|
167
|
+
|
|
168
|
+
// Build component summaries
|
|
169
|
+
const summaries = segments.map((s) => buildComponentSummary(s));
|
|
170
|
+
|
|
171
|
+
// Calculate coverage
|
|
172
|
+
const coverage = calculateCoverage(segments, summaries);
|
|
173
|
+
|
|
174
|
+
// Calculate quality insights
|
|
175
|
+
const quality = calculateQuality(segments, summaries);
|
|
176
|
+
|
|
177
|
+
// Build distribution data
|
|
178
|
+
const distribution = buildDistribution(segments, summaries);
|
|
179
|
+
|
|
180
|
+
// Build inventory
|
|
181
|
+
const inventory = buildInventory(summaries);
|
|
182
|
+
|
|
183
|
+
// Generate recommendations
|
|
184
|
+
const recommendations = generateRecommendations(coverage, quality, summaries);
|
|
185
|
+
|
|
186
|
+
// Calculate overall score
|
|
187
|
+
const overallScore = calculateOverallScore(coverage, quality, summaries);
|
|
188
|
+
|
|
189
|
+
// Collect all categories
|
|
190
|
+
const categories = [...new Set(segments.map((s) => s.meta.category))].sort();
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
analyzedAt,
|
|
194
|
+
summary: {
|
|
195
|
+
totalComponents: segments.length,
|
|
196
|
+
totalVariants: segments.reduce((sum, s) => sum + s.variants.length, 0),
|
|
197
|
+
totalProps: segments.reduce(
|
|
198
|
+
(sum, s) => sum + Object.keys(s.props ?? {}).length,
|
|
199
|
+
0
|
|
200
|
+
),
|
|
201
|
+
categories,
|
|
202
|
+
overallScore,
|
|
203
|
+
},
|
|
204
|
+
inventory,
|
|
205
|
+
coverage,
|
|
206
|
+
quality,
|
|
207
|
+
distribution,
|
|
208
|
+
recommendations,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Build summary for a single component
|
|
214
|
+
*/
|
|
215
|
+
function buildComponentSummary(segment: CompiledSegment): ComponentSummary {
|
|
216
|
+
const propCount = Object.keys(segment.props ?? {}).length;
|
|
217
|
+
const hasUsageWhen = (segment.usage?.when?.length ?? 0) > 0;
|
|
218
|
+
const hasUsageWhenNot = (segment.usage?.whenNot?.length ?? 0) > 0;
|
|
219
|
+
const hasGuidelines = (segment.usage?.guidelines?.length ?? 0) > 0;
|
|
220
|
+
const hasRelations = (segment.relations?.length ?? 0) > 0;
|
|
221
|
+
|
|
222
|
+
// Calculate documentation score
|
|
223
|
+
let docScore = 0;
|
|
224
|
+
let docTotal = 0;
|
|
225
|
+
|
|
226
|
+
// Description (20 points)
|
|
227
|
+
docTotal += 20;
|
|
228
|
+
if (segment.meta.description && segment.meta.description.length > 20) {
|
|
229
|
+
docScore += 20;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Usage when (20 points)
|
|
233
|
+
docTotal += 20;
|
|
234
|
+
if (hasUsageWhen) docScore += 20;
|
|
235
|
+
|
|
236
|
+
// Usage whenNot (20 points) - key differentiator
|
|
237
|
+
docTotal += 20;
|
|
238
|
+
if (hasUsageWhenNot) docScore += 20;
|
|
239
|
+
|
|
240
|
+
// Guidelines (15 points)
|
|
241
|
+
docTotal += 15;
|
|
242
|
+
if (hasGuidelines) docScore += 15;
|
|
243
|
+
|
|
244
|
+
// Relations (10 points)
|
|
245
|
+
docTotal += 10;
|
|
246
|
+
if (hasRelations) docScore += 10;
|
|
247
|
+
|
|
248
|
+
// Props documented (15 points)
|
|
249
|
+
docTotal += 15;
|
|
250
|
+
if (propCount > 0) {
|
|
251
|
+
const documentedProps = Object.values(segment.props ?? {}).filter(
|
|
252
|
+
(p) => p.description && p.description.length > 5
|
|
253
|
+
).length;
|
|
254
|
+
docScore += Math.round((documentedProps / propCount) * 15);
|
|
255
|
+
} else {
|
|
256
|
+
docScore += 15; // No props = full score for this section
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
name: segment.meta.name,
|
|
261
|
+
category: segment.meta.category,
|
|
262
|
+
status: segment.meta.status ?? "stable",
|
|
263
|
+
variantCount: segment.variants.length,
|
|
264
|
+
propCount,
|
|
265
|
+
hasUsageWhen,
|
|
266
|
+
hasUsageWhenNot,
|
|
267
|
+
hasGuidelines,
|
|
268
|
+
hasRelations,
|
|
269
|
+
documentationScore: Math.round((docScore / docTotal) * 100),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Calculate coverage analytics
|
|
275
|
+
*/
|
|
276
|
+
function calculateCoverage(
|
|
277
|
+
segments: CompiledSegment[],
|
|
278
|
+
summaries: ComponentSummary[]
|
|
279
|
+
): CoverageAnalytics {
|
|
280
|
+
const total = segments.length;
|
|
281
|
+
|
|
282
|
+
const fields = {
|
|
283
|
+
description: {
|
|
284
|
+
covered: segments.filter(
|
|
285
|
+
(s) => s.meta.description && s.meta.description.length > 10
|
|
286
|
+
).length,
|
|
287
|
+
total,
|
|
288
|
+
percentage: 0,
|
|
289
|
+
},
|
|
290
|
+
usageWhen: {
|
|
291
|
+
covered: summaries.filter((s) => s.hasUsageWhen).length,
|
|
292
|
+
total,
|
|
293
|
+
percentage: 0,
|
|
294
|
+
},
|
|
295
|
+
usageWhenNot: {
|
|
296
|
+
covered: summaries.filter((s) => s.hasUsageWhenNot).length,
|
|
297
|
+
total,
|
|
298
|
+
percentage: 0,
|
|
299
|
+
},
|
|
300
|
+
guidelines: {
|
|
301
|
+
covered: summaries.filter((s) => s.hasGuidelines).length,
|
|
302
|
+
total,
|
|
303
|
+
percentage: 0,
|
|
304
|
+
},
|
|
305
|
+
accessibility: {
|
|
306
|
+
covered: segments.filter((s) => (s.usage?.accessibility?.length ?? 0) > 0)
|
|
307
|
+
.length,
|
|
308
|
+
total,
|
|
309
|
+
percentage: 0,
|
|
310
|
+
},
|
|
311
|
+
relations: {
|
|
312
|
+
covered: summaries.filter((s) => s.hasRelations).length,
|
|
313
|
+
total,
|
|
314
|
+
percentage: 0,
|
|
315
|
+
},
|
|
316
|
+
propDescriptions: {
|
|
317
|
+
covered: 0,
|
|
318
|
+
total: 0,
|
|
319
|
+
percentage: 0,
|
|
320
|
+
},
|
|
321
|
+
propConstraints: {
|
|
322
|
+
covered: 0,
|
|
323
|
+
total: 0,
|
|
324
|
+
percentage: 0,
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// Calculate prop coverage
|
|
329
|
+
for (const segment of segments) {
|
|
330
|
+
const props = Object.values(segment.props ?? {});
|
|
331
|
+
fields.propDescriptions.total += props.length;
|
|
332
|
+
fields.propConstraints.total += props.length;
|
|
333
|
+
|
|
334
|
+
fields.propDescriptions.covered += props.filter(
|
|
335
|
+
(p) => p.description && p.description.length > 5
|
|
336
|
+
).length;
|
|
337
|
+
fields.propConstraints.covered += props.filter(
|
|
338
|
+
(p) => (p.constraints?.length ?? 0) > 0
|
|
339
|
+
).length;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Calculate percentages
|
|
343
|
+
for (const field of Object.values(fields)) {
|
|
344
|
+
field.percentage =
|
|
345
|
+
field.total > 0 ? Math.round((field.covered / field.total) * 100) : 100;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Find incomplete components
|
|
349
|
+
const incomplete: CoverageAnalytics["incomplete"] = [];
|
|
350
|
+
for (const summary of summaries) {
|
|
351
|
+
const missing: string[] = [];
|
|
352
|
+
if (!summary.hasUsageWhen) missing.push("usage.when");
|
|
353
|
+
if (!summary.hasUsageWhenNot) missing.push("usage.whenNot");
|
|
354
|
+
if (!summary.hasGuidelines) missing.push("usage.guidelines");
|
|
355
|
+
if (!summary.hasRelations) missing.push("relations");
|
|
356
|
+
|
|
357
|
+
if (missing.length > 0) {
|
|
358
|
+
incomplete.push({ component: summary.name, missing });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Overall coverage (weighted average)
|
|
363
|
+
const overall = Math.round(
|
|
364
|
+
fields.description.percentage * 0.1 +
|
|
365
|
+
fields.usageWhen.percentage * 0.2 +
|
|
366
|
+
fields.usageWhenNot.percentage * 0.25 +
|
|
367
|
+
fields.guidelines.percentage * 0.15 +
|
|
368
|
+
fields.relations.percentage * 0.1 +
|
|
369
|
+
fields.propDescriptions.percentage * 0.15 +
|
|
370
|
+
fields.propConstraints.percentage * 0.05
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
return { overall, fields, incomplete };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Calculate quality analytics
|
|
378
|
+
*/
|
|
379
|
+
function calculateQuality(
|
|
380
|
+
segments: CompiledSegment[],
|
|
381
|
+
summaries: ComponentSummary[]
|
|
382
|
+
): QualityAnalytics {
|
|
383
|
+
const missingWhenNot = summaries
|
|
384
|
+
.filter((s) => !s.hasUsageWhenNot)
|
|
385
|
+
.map((s) => s.name);
|
|
386
|
+
|
|
387
|
+
const isolated = summaries
|
|
388
|
+
.filter((s) => !s.hasRelations)
|
|
389
|
+
.map((s) => s.name);
|
|
390
|
+
|
|
391
|
+
const deprecated = summaries
|
|
392
|
+
.filter((s) => s.status === "deprecated")
|
|
393
|
+
.map((s) => s.name);
|
|
394
|
+
|
|
395
|
+
const fewVariants = summaries
|
|
396
|
+
.filter((s) => s.variantCount < 2)
|
|
397
|
+
.map((s) => s.name);
|
|
398
|
+
|
|
399
|
+
const undocumentedProps: QualityAnalytics["undocumentedProps"] = [];
|
|
400
|
+
const unconstrainedProps: QualityAnalytics["unconstrainedProps"] = [];
|
|
401
|
+
|
|
402
|
+
for (const segment of segments) {
|
|
403
|
+
for (const [propName, prop] of Object.entries(segment.props ?? {})) {
|
|
404
|
+
if (!prop.description || prop.description.length < 5) {
|
|
405
|
+
undocumentedProps.push({ component: segment.meta.name, prop: propName });
|
|
406
|
+
}
|
|
407
|
+
if (
|
|
408
|
+
(prop.constraints?.length ?? 0) === 0 &&
|
|
409
|
+
prop.type !== "boolean" &&
|
|
410
|
+
prop.type !== "function"
|
|
411
|
+
) {
|
|
412
|
+
unconstrainedProps.push({
|
|
413
|
+
component: segment.meta.name,
|
|
414
|
+
prop: propName,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
missingWhenNot,
|
|
422
|
+
isolated,
|
|
423
|
+
deprecated,
|
|
424
|
+
fewVariants,
|
|
425
|
+
undocumentedProps,
|
|
426
|
+
unconstrainedProps,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Build distribution data for charts
|
|
432
|
+
*/
|
|
433
|
+
function buildDistribution(
|
|
434
|
+
segments: CompiledSegment[],
|
|
435
|
+
summaries: ComponentSummary[]
|
|
436
|
+
): DistributionAnalytics {
|
|
437
|
+
// Variants per component
|
|
438
|
+
const variantsPerComponent = summaries
|
|
439
|
+
.map((s) => ({ name: s.name, count: s.variantCount }))
|
|
440
|
+
.sort((a, b) => b.count - a.count);
|
|
441
|
+
|
|
442
|
+
// Props per component
|
|
443
|
+
const propsPerComponent = summaries
|
|
444
|
+
.map((s) => ({ name: s.name, count: s.propCount }))
|
|
445
|
+
.sort((a, b) => b.count - a.count);
|
|
446
|
+
|
|
447
|
+
// Components per category
|
|
448
|
+
const categoryMap = new Map<string, number>();
|
|
449
|
+
for (const s of summaries) {
|
|
450
|
+
categoryMap.set(s.category, (categoryMap.get(s.category) ?? 0) + 1);
|
|
451
|
+
}
|
|
452
|
+
const componentsPerCategory = Array.from(categoryMap.entries())
|
|
453
|
+
.map(([category, count]) => ({ category, count }))
|
|
454
|
+
.sort((a, b) => b.count - a.count);
|
|
455
|
+
|
|
456
|
+
// Status distribution
|
|
457
|
+
const statusMap = new Map<string, number>();
|
|
458
|
+
for (const s of summaries) {
|
|
459
|
+
statusMap.set(s.status, (statusMap.get(s.status) ?? 0) + 1);
|
|
460
|
+
}
|
|
461
|
+
const statusDistribution = Array.from(statusMap.entries())
|
|
462
|
+
.map(([status, count]) => ({ status, count }))
|
|
463
|
+
.sort((a, b) => b.count - a.count);
|
|
464
|
+
|
|
465
|
+
// Tag frequency
|
|
466
|
+
const tagMap = new Map<string, number>();
|
|
467
|
+
for (const segment of segments) {
|
|
468
|
+
for (const tag of segment.meta.tags ?? []) {
|
|
469
|
+
tagMap.set(tag, (tagMap.get(tag) ?? 0) + 1);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const tagFrequency = Array.from(tagMap.entries())
|
|
473
|
+
.map(([tag, count]) => ({ tag, count }))
|
|
474
|
+
.sort((a, b) => b.count - a.count);
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
variantsPerComponent,
|
|
478
|
+
propsPerComponent,
|
|
479
|
+
componentsPerCategory,
|
|
480
|
+
statusDistribution,
|
|
481
|
+
tagFrequency,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Build inventory breakdown
|
|
487
|
+
*/
|
|
488
|
+
function buildInventory(summaries: ComponentSummary[]): ComponentInventory {
|
|
489
|
+
const byCategory: Record<string, ComponentSummary[]> = {};
|
|
490
|
+
const byStatus: Record<string, ComponentSummary[]> = {};
|
|
491
|
+
|
|
492
|
+
for (const summary of summaries) {
|
|
493
|
+
if (!byCategory[summary.category]) {
|
|
494
|
+
byCategory[summary.category] = [];
|
|
495
|
+
}
|
|
496
|
+
byCategory[summary.category].push(summary);
|
|
497
|
+
|
|
498
|
+
if (!byStatus[summary.status]) {
|
|
499
|
+
byStatus[summary.status] = [];
|
|
500
|
+
}
|
|
501
|
+
byStatus[summary.status].push(summary);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Sort within each group by name
|
|
505
|
+
for (const list of Object.values(byCategory)) {
|
|
506
|
+
list.sort((a, b) => a.name.localeCompare(b.name));
|
|
507
|
+
}
|
|
508
|
+
for (const list of Object.values(byStatus)) {
|
|
509
|
+
list.sort((a, b) => a.name.localeCompare(b.name));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
byCategory,
|
|
514
|
+
byStatus,
|
|
515
|
+
byVariantCount: [...summaries].sort((a, b) => b.variantCount - a.variantCount),
|
|
516
|
+
byPropCount: [...summaries].sort((a, b) => b.propCount - a.propCount),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Generate actionable recommendations
|
|
522
|
+
*/
|
|
523
|
+
function generateRecommendations(
|
|
524
|
+
coverage: CoverageAnalytics,
|
|
525
|
+
quality: QualityAnalytics,
|
|
526
|
+
summaries: ComponentSummary[]
|
|
527
|
+
): Recommendation[] {
|
|
528
|
+
const recommendations: Recommendation[] = [];
|
|
529
|
+
|
|
530
|
+
// High priority: Missing whenNot guidance
|
|
531
|
+
if (quality.missingWhenNot.length > 0) {
|
|
532
|
+
recommendations.push({
|
|
533
|
+
priority: "high",
|
|
534
|
+
category: "documentation",
|
|
535
|
+
title: 'Add "When NOT to use" guidance',
|
|
536
|
+
description: `${quality.missingWhenNot.length} component(s) lack guidance on when NOT to use them. This is critical for AI agents to make correct component choices.`,
|
|
537
|
+
components: quality.missingWhenNot.slice(0, 5),
|
|
538
|
+
impact: "Prevents AI from using components incorrectly",
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// High priority: Low overall coverage
|
|
543
|
+
if (coverage.overall < 70) {
|
|
544
|
+
recommendations.push({
|
|
545
|
+
priority: "high",
|
|
546
|
+
category: "coverage",
|
|
547
|
+
title: "Improve documentation coverage",
|
|
548
|
+
description: `Overall documentation coverage is ${coverage.overall}%. Aim for at least 80% for effective AI assistance.`,
|
|
549
|
+
impact: "Better AI understanding and suggestions",
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Medium priority: Isolated components
|
|
554
|
+
if (quality.isolated.length > summaries.length * 0.3) {
|
|
555
|
+
recommendations.push({
|
|
556
|
+
priority: "medium",
|
|
557
|
+
category: "organization",
|
|
558
|
+
title: "Add component relationships",
|
|
559
|
+
description: `${quality.isolated.length} component(s) have no relationships defined. Adding relations helps AI understand component ecosystems.`,
|
|
560
|
+
components: quality.isolated.slice(0, 5),
|
|
561
|
+
impact: "Better alternative suggestions",
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Medium priority: Few variants
|
|
566
|
+
if (quality.fewVariants.length > 0) {
|
|
567
|
+
recommendations.push({
|
|
568
|
+
priority: "medium",
|
|
569
|
+
category: "coverage",
|
|
570
|
+
title: "Add more variant examples",
|
|
571
|
+
description: `${quality.fewVariants.length} component(s) have fewer than 2 variants. More examples help demonstrate component flexibility.`,
|
|
572
|
+
components: quality.fewVariants.slice(0, 5),
|
|
573
|
+
impact: "Better visual documentation",
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Low priority: Undocumented props
|
|
578
|
+
if (quality.undocumentedProps.length > 0) {
|
|
579
|
+
recommendations.push({
|
|
580
|
+
priority: "low",
|
|
581
|
+
category: "documentation",
|
|
582
|
+
title: "Document prop descriptions",
|
|
583
|
+
description: `${quality.undocumentedProps.length} prop(s) lack descriptions. Clear descriptions help AI generate correct code.`,
|
|
584
|
+
impact: "Clearer API documentation",
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Low priority: No constraints
|
|
589
|
+
if (quality.unconstrainedProps.length > 5) {
|
|
590
|
+
recommendations.push({
|
|
591
|
+
priority: "low",
|
|
592
|
+
category: "quality",
|
|
593
|
+
title: "Add prop constraints",
|
|
594
|
+
description: `${quality.unconstrainedProps.length} prop(s) could benefit from constraint documentation (validation rules, best practices).`,
|
|
595
|
+
impact: "Better AI adherence to design rules",
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return recommendations;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Calculate overall design system quality score
|
|
604
|
+
*/
|
|
605
|
+
function calculateOverallScore(
|
|
606
|
+
coverage: CoverageAnalytics,
|
|
607
|
+
quality: QualityAnalytics,
|
|
608
|
+
summaries: ComponentSummary[]
|
|
609
|
+
): number {
|
|
610
|
+
// Base: coverage score (50%)
|
|
611
|
+
let score = coverage.overall * 0.5;
|
|
612
|
+
|
|
613
|
+
// Component documentation scores (30%)
|
|
614
|
+
const avgDocScore =
|
|
615
|
+
summaries.reduce((sum, s) => sum + s.documentationScore, 0) /
|
|
616
|
+
Math.max(summaries.length, 1);
|
|
617
|
+
score += avgDocScore * 0.3;
|
|
618
|
+
|
|
619
|
+
// Quality penalties (20%)
|
|
620
|
+
let qualityScore = 100;
|
|
621
|
+
|
|
622
|
+
// Penalize missing whenNot (most important)
|
|
623
|
+
const missingWhenNotRatio =
|
|
624
|
+
quality.missingWhenNot.length / Math.max(summaries.length, 1);
|
|
625
|
+
qualityScore -= missingWhenNotRatio * 40;
|
|
626
|
+
|
|
627
|
+
// Penalize isolated components
|
|
628
|
+
const isolatedRatio = quality.isolated.length / Math.max(summaries.length, 1);
|
|
629
|
+
qualityScore -= isolatedRatio * 20;
|
|
630
|
+
|
|
631
|
+
// Penalize few variants
|
|
632
|
+
const fewVariantsRatio =
|
|
633
|
+
quality.fewVariants.length / Math.max(summaries.length, 1);
|
|
634
|
+
qualityScore -= fewVariantsRatio * 10;
|
|
635
|
+
|
|
636
|
+
score += Math.max(qualityScore, 0) * 0.2;
|
|
637
|
+
|
|
638
|
+
return Math.round(Math.max(Math.min(score, 100), 0));
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Get a letter grade from a numeric score
|
|
643
|
+
*/
|
|
644
|
+
export function getGrade(score: number): string {
|
|
645
|
+
if (score >= 90) return "A";
|
|
646
|
+
if (score >= 80) return "B";
|
|
647
|
+
if (score >= 70) return "C";
|
|
648
|
+
if (score >= 60) return "D";
|
|
649
|
+
return "F";
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Get a color for a score (for HTML reports)
|
|
654
|
+
*/
|
|
655
|
+
export function getScoreColor(score: number): string {
|
|
656
|
+
if (score >= 80) return "#22c55e"; // green
|
|
657
|
+
if (score >= 60) return "#eab308"; // yellow
|
|
658
|
+
return "#ef4444"; // red
|
|
659
|
+
}
|