@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,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage Pattern Aggregator
|
|
3
|
+
*
|
|
4
|
+
* Aggregates component usages into patterns for AI analysis.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
ComponentUsage,
|
|
9
|
+
ComponentImport,
|
|
10
|
+
ComponentAnalysis,
|
|
11
|
+
UsagePattern,
|
|
12
|
+
FileContext,
|
|
13
|
+
UsageAnalysis,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Aggregate usages for a single component into patterns
|
|
18
|
+
*/
|
|
19
|
+
export function aggregateComponentUsages(
|
|
20
|
+
componentName: string,
|
|
21
|
+
sourceFile: string,
|
|
22
|
+
usages: ComponentUsage[],
|
|
23
|
+
imports: ComponentImport[]
|
|
24
|
+
): ComponentAnalysis {
|
|
25
|
+
// Count unique files
|
|
26
|
+
const uniqueFiles = new Set(usages.map((u) => u.filePath));
|
|
27
|
+
|
|
28
|
+
// Group usages by prop pattern
|
|
29
|
+
const patternMap = new Map<string, UsagePattern>();
|
|
30
|
+
|
|
31
|
+
for (const usage of usages) {
|
|
32
|
+
// Create a key from static props (sorted for consistency)
|
|
33
|
+
const propKey = createPropKey(usage.props.static);
|
|
34
|
+
|
|
35
|
+
const existing = patternMap.get(propKey);
|
|
36
|
+
if (existing) {
|
|
37
|
+
existing.count++;
|
|
38
|
+
if (!existing.files.includes(usage.filePath)) {
|
|
39
|
+
existing.files.push(usage.filePath);
|
|
40
|
+
}
|
|
41
|
+
if (existing.sampleContexts.length < 3) {
|
|
42
|
+
existing.sampleContexts.push(usage.context);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
patternMap.set(propKey, {
|
|
46
|
+
props: { ...usage.props.static },
|
|
47
|
+
count: 1,
|
|
48
|
+
files: [usage.filePath],
|
|
49
|
+
sampleContexts: [usage.context],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Sort patterns by frequency
|
|
55
|
+
const patterns = Array.from(patternMap.values()).sort(
|
|
56
|
+
(a, b) => b.count - a.count
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Classify file contexts
|
|
60
|
+
const contextMap = new Map<FileContext, { count: number; files: string[] }>();
|
|
61
|
+
|
|
62
|
+
for (const usage of usages) {
|
|
63
|
+
const context = classifyFileContext(usage.filePath, usage.context);
|
|
64
|
+
const existing = contextMap.get(context);
|
|
65
|
+
if (existing) {
|
|
66
|
+
existing.count++;
|
|
67
|
+
if (!existing.files.includes(usage.filePath)) {
|
|
68
|
+
existing.files.push(usage.filePath);
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
contextMap.set(context, { count: 1, files: [usage.filePath] });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Convert to sorted array
|
|
76
|
+
const contexts = Array.from(contextMap.entries())
|
|
77
|
+
.map(([type, data]) => ({ type, ...data }))
|
|
78
|
+
.sort((a, b) => b.count - a.count);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
name: componentName,
|
|
82
|
+
sourceFile,
|
|
83
|
+
totalUsages: usages.length,
|
|
84
|
+
uniqueFiles: uniqueFiles.size,
|
|
85
|
+
patterns,
|
|
86
|
+
contexts,
|
|
87
|
+
usages,
|
|
88
|
+
imports,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Aggregate all usages across the codebase
|
|
94
|
+
*/
|
|
95
|
+
export function aggregateAllUsages(
|
|
96
|
+
usages: ComponentUsage[],
|
|
97
|
+
imports: ComponentImport[],
|
|
98
|
+
componentSources: Map<string, string>
|
|
99
|
+
): UsageAnalysis {
|
|
100
|
+
// Group usages by component
|
|
101
|
+
const usagesByComponent = new Map<string, ComponentUsage[]>();
|
|
102
|
+
for (const usage of usages) {
|
|
103
|
+
const existing = usagesByComponent.get(usage.componentName) ?? [];
|
|
104
|
+
existing.push(usage);
|
|
105
|
+
usagesByComponent.set(usage.componentName, existing);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Group imports by component
|
|
109
|
+
const importsByComponent = new Map<string, ComponentImport[]>();
|
|
110
|
+
for (const imp of imports) {
|
|
111
|
+
const existing = importsByComponent.get(imp.componentName) ?? [];
|
|
112
|
+
existing.push(imp);
|
|
113
|
+
importsByComponent.set(imp.componentName, existing);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Build component analyses
|
|
117
|
+
const components: Record<string, ComponentAnalysis> = {};
|
|
118
|
+
const allComponentNames = new Set([
|
|
119
|
+
...usagesByComponent.keys(),
|
|
120
|
+
...importsByComponent.keys(),
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
for (const componentName of allComponentNames) {
|
|
124
|
+
const componentUsages = usagesByComponent.get(componentName) ?? [];
|
|
125
|
+
const componentImports = importsByComponent.get(componentName) ?? [];
|
|
126
|
+
const sourceFile = componentSources.get(componentName) ?? "unknown";
|
|
127
|
+
|
|
128
|
+
components[componentName] = aggregateComponentUsages(
|
|
129
|
+
componentName,
|
|
130
|
+
sourceFile,
|
|
131
|
+
componentUsages,
|
|
132
|
+
componentImports
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Collect unique files
|
|
137
|
+
const allFiles = new Set<string>();
|
|
138
|
+
for (const usage of usages) {
|
|
139
|
+
allFiles.add(usage.filePath);
|
|
140
|
+
}
|
|
141
|
+
for (const imp of imports) {
|
|
142
|
+
allFiles.add(imp.filePath);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
timestamp: new Date().toISOString(),
|
|
147
|
+
rootDir: "", // Will be set by caller
|
|
148
|
+
totalFiles: allFiles.size,
|
|
149
|
+
totalComponents: Object.keys(components).length,
|
|
150
|
+
components,
|
|
151
|
+
errorFiles: [], // Will be populated by scanner
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Create a stable key from props for pattern grouping
|
|
157
|
+
*/
|
|
158
|
+
function createPropKey(props: Record<string, string | boolean | number>): string {
|
|
159
|
+
const entries = Object.entries(props).sort(([a], [b]) => a.localeCompare(b));
|
|
160
|
+
return entries.map(([k, v]) => `${k}=${JSON.stringify(v)}`).join("|");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Classify file context based on path and content
|
|
165
|
+
*/
|
|
166
|
+
function classifyFileContext(filePath: string, context: string): FileContext {
|
|
167
|
+
const lowerPath = filePath.toLowerCase();
|
|
168
|
+
const lowerContext = context.toLowerCase();
|
|
169
|
+
|
|
170
|
+
// Check path patterns first
|
|
171
|
+
if (
|
|
172
|
+
lowerPath.includes("/modal") ||
|
|
173
|
+
lowerPath.includes("modal.") ||
|
|
174
|
+
lowerContext.includes("<modal")
|
|
175
|
+
) {
|
|
176
|
+
return "modal";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
lowerPath.includes("/dialog") ||
|
|
181
|
+
lowerPath.includes("dialog.") ||
|
|
182
|
+
lowerContext.includes("<dialog")
|
|
183
|
+
) {
|
|
184
|
+
return "dialog";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (
|
|
188
|
+
lowerPath.includes("/form") ||
|
|
189
|
+
lowerPath.includes("form.") ||
|
|
190
|
+
lowerContext.includes("<form") ||
|
|
191
|
+
lowerContext.includes("onsubmit")
|
|
192
|
+
) {
|
|
193
|
+
return "form";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (
|
|
197
|
+
lowerPath.includes("/page") ||
|
|
198
|
+
lowerPath.includes("pages/") ||
|
|
199
|
+
lowerPath.includes("/routes/")
|
|
200
|
+
) {
|
|
201
|
+
return "page";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
lowerPath.includes("/layout") ||
|
|
206
|
+
lowerPath.includes("layout.") ||
|
|
207
|
+
lowerPath.includes("/layouts/")
|
|
208
|
+
) {
|
|
209
|
+
return "layout";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
lowerPath.includes("/card") ||
|
|
214
|
+
lowerPath.includes("card.") ||
|
|
215
|
+
lowerContext.includes("<card")
|
|
216
|
+
) {
|
|
217
|
+
return "card";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
lowerPath.includes("/table") ||
|
|
222
|
+
lowerPath.includes("table.") ||
|
|
223
|
+
lowerContext.includes("<table") ||
|
|
224
|
+
lowerContext.includes("<tr")
|
|
225
|
+
) {
|
|
226
|
+
return "table";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (
|
|
230
|
+
lowerPath.includes("/list") ||
|
|
231
|
+
lowerPath.includes("list.") ||
|
|
232
|
+
lowerContext.includes("<ul") ||
|
|
233
|
+
lowerContext.includes("<ol") ||
|
|
234
|
+
lowerContext.includes("<li")
|
|
235
|
+
) {
|
|
236
|
+
return "list";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (
|
|
240
|
+
lowerPath.includes("/nav") ||
|
|
241
|
+
lowerPath.includes("navigation") ||
|
|
242
|
+
lowerContext.includes("<nav")
|
|
243
|
+
) {
|
|
244
|
+
return "navigation";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (
|
|
248
|
+
lowerPath.includes("/header") ||
|
|
249
|
+
lowerPath.includes("header.") ||
|
|
250
|
+
lowerContext.includes("<header")
|
|
251
|
+
) {
|
|
252
|
+
return "header";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (
|
|
256
|
+
lowerPath.includes("/footer") ||
|
|
257
|
+
lowerPath.includes("footer.") ||
|
|
258
|
+
lowerContext.includes("<footer")
|
|
259
|
+
) {
|
|
260
|
+
return "footer";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (
|
|
264
|
+
lowerPath.includes("/sidebar") ||
|
|
265
|
+
lowerPath.includes("sidebar.") ||
|
|
266
|
+
lowerContext.includes("<aside")
|
|
267
|
+
) {
|
|
268
|
+
return "sidebar";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return "unknown";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Find common prop combinations across usages
|
|
276
|
+
*/
|
|
277
|
+
export function findCommonPropCombinations(
|
|
278
|
+
usages: ComponentUsage[],
|
|
279
|
+
minOccurrences: number = 3
|
|
280
|
+
): { props: string[]; count: number }[] {
|
|
281
|
+
const combinations = new Map<string, number>();
|
|
282
|
+
|
|
283
|
+
for (const usage of usages) {
|
|
284
|
+
// Get all prop names (static and dynamic)
|
|
285
|
+
const allProps = [
|
|
286
|
+
...Object.keys(usage.props.static),
|
|
287
|
+
...usage.props.dynamic,
|
|
288
|
+
].sort();
|
|
289
|
+
|
|
290
|
+
// Generate all combinations of 2+ props
|
|
291
|
+
for (let size = 2; size <= Math.min(allProps.length, 4); size++) {
|
|
292
|
+
for (const combo of combinations_k(allProps, size)) {
|
|
293
|
+
const key = combo.join(",");
|
|
294
|
+
combinations.set(key, (combinations.get(key) ?? 0) + 1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Filter and sort by frequency
|
|
300
|
+
return Array.from(combinations.entries())
|
|
301
|
+
.filter(([, count]) => count >= minOccurrences)
|
|
302
|
+
.map(([key, count]) => ({ props: key.split(","), count }))
|
|
303
|
+
.sort((a, b) => b.count - a.count);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Generate k-combinations of an array
|
|
308
|
+
*/
|
|
309
|
+
function* combinations_k<T>(arr: T[], k: number): Generator<T[]> {
|
|
310
|
+
if (k === 0) {
|
|
311
|
+
yield [];
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (arr.length < k) return;
|
|
315
|
+
|
|
316
|
+
for (let i = 0; i <= arr.length - k; i++) {
|
|
317
|
+
for (const combo of combinations_k(arr.slice(i + 1), k - 1)) {
|
|
318
|
+
yield [arr[i], ...combo];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Component relationship with frequency data
|
|
325
|
+
*/
|
|
326
|
+
export interface ComponentRelation {
|
|
327
|
+
/** Related component name */
|
|
328
|
+
component: string;
|
|
329
|
+
/** Type of relationship */
|
|
330
|
+
relationship: "parent" | "sibling" | "child";
|
|
331
|
+
/** How often this relationship occurs */
|
|
332
|
+
frequency: number;
|
|
333
|
+
/** Sample files where this relationship occurs */
|
|
334
|
+
sampleFiles: string[];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Infer component relationships from usage patterns
|
|
339
|
+
*
|
|
340
|
+
* Analyzes parent/sibling relationships from actual usage data:
|
|
341
|
+
* - parent: Components that contain this component (from parentElement)
|
|
342
|
+
* - sibling: Components that appear alongside this component in the same parent
|
|
343
|
+
*/
|
|
344
|
+
export function inferRelations(
|
|
345
|
+
componentName: string,
|
|
346
|
+
analysis: ComponentAnalysis,
|
|
347
|
+
allAnalyses: Record<string, ComponentAnalysis>
|
|
348
|
+
): ComponentRelation[] {
|
|
349
|
+
const relations: ComponentRelation[] = [];
|
|
350
|
+
const parentCounts = new Map<string, { count: number; files: string[] }>();
|
|
351
|
+
const siblingCounts = new Map<string, { count: number; files: string[] }>();
|
|
352
|
+
|
|
353
|
+
// Count parent relationships
|
|
354
|
+
for (const usage of analysis.usages) {
|
|
355
|
+
if (usage.parentElement && usage.parentElement !== "div" && usage.parentElement !== "span") {
|
|
356
|
+
const parent = usage.parentElement;
|
|
357
|
+
const existing = parentCounts.get(parent);
|
|
358
|
+
if (existing) {
|
|
359
|
+
existing.count++;
|
|
360
|
+
if (!existing.files.includes(usage.filePath)) {
|
|
361
|
+
existing.files.push(usage.filePath);
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
parentCounts.set(parent, { count: 1, files: [usage.filePath] });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Infer sibling relationships: other components used in the same files
|
|
370
|
+
const filesWithThisComponent = new Set(analysis.usages.map((u) => u.filePath));
|
|
371
|
+
|
|
372
|
+
for (const [otherName, otherAnalysis] of Object.entries(allAnalyses)) {
|
|
373
|
+
if (otherName === componentName) continue;
|
|
374
|
+
|
|
375
|
+
// Count how many files have both components
|
|
376
|
+
let sharedFiles = 0;
|
|
377
|
+
const sharedFileList: string[] = [];
|
|
378
|
+
|
|
379
|
+
for (const usage of otherAnalysis.usages) {
|
|
380
|
+
if (filesWithThisComponent.has(usage.filePath)) {
|
|
381
|
+
if (!sharedFileList.includes(usage.filePath)) {
|
|
382
|
+
sharedFiles++;
|
|
383
|
+
sharedFileList.push(usage.filePath);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Only include if they appear together frequently
|
|
389
|
+
if (sharedFiles >= 3) {
|
|
390
|
+
siblingCounts.set(otherName, {
|
|
391
|
+
count: sharedFiles,
|
|
392
|
+
files: sharedFileList.slice(0, 5),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Convert to relations array (parents)
|
|
398
|
+
for (const [parent, data] of parentCounts) {
|
|
399
|
+
// Only include meaningful parent relationships (count >= 2)
|
|
400
|
+
if (data.count >= 2) {
|
|
401
|
+
relations.push({
|
|
402
|
+
component: parent,
|
|
403
|
+
relationship: "parent",
|
|
404
|
+
frequency: data.count,
|
|
405
|
+
sampleFiles: data.files.slice(0, 3),
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Convert to relations array (siblings)
|
|
411
|
+
for (const [sibling, data] of siblingCounts) {
|
|
412
|
+
relations.push({
|
|
413
|
+
component: sibling,
|
|
414
|
+
relationship: "sibling",
|
|
415
|
+
frequency: data.count,
|
|
416
|
+
sampleFiles: data.files.slice(0, 3),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Sort by frequency
|
|
421
|
+
relations.sort((a, b) => b.frequency - a.frequency);
|
|
422
|
+
|
|
423
|
+
return relations;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Infer relations for all components in the analysis
|
|
428
|
+
*/
|
|
429
|
+
export function inferAllRelations(
|
|
430
|
+
analysis: UsageAnalysis
|
|
431
|
+
): Map<string, ComponentRelation[]> {
|
|
432
|
+
const allRelations = new Map<string, ComponentRelation[]>();
|
|
433
|
+
|
|
434
|
+
for (const [componentName, componentAnalysis] of Object.entries(analysis.components)) {
|
|
435
|
+
const relations = inferRelations(componentName, componentAnalysis, analysis.components);
|
|
436
|
+
if (relations.length > 0) {
|
|
437
|
+
allRelations.set(componentName, relations);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return allRelations;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Summarize patterns for AI prompt
|
|
446
|
+
*/
|
|
447
|
+
export function summarizePatternsForPrompt(
|
|
448
|
+
analysis: ComponentAnalysis,
|
|
449
|
+
maxPatterns: number = 10,
|
|
450
|
+
maxContexts: number = 5
|
|
451
|
+
): string {
|
|
452
|
+
const lines: string[] = [];
|
|
453
|
+
|
|
454
|
+
lines.push(`## ${analysis.name}`);
|
|
455
|
+
lines.push(`Total usages: ${analysis.totalUsages} across ${analysis.uniqueFiles} files`);
|
|
456
|
+
lines.push("");
|
|
457
|
+
|
|
458
|
+
// Top patterns
|
|
459
|
+
lines.push("### Usage Patterns (by frequency)");
|
|
460
|
+
const topPatterns = analysis.patterns.slice(0, maxPatterns);
|
|
461
|
+
for (const pattern of topPatterns) {
|
|
462
|
+
const propsStr = Object.entries(pattern.props)
|
|
463
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
464
|
+
.join(", ");
|
|
465
|
+
lines.push(`- ${propsStr || "(no props)"}: ${pattern.count} usages in ${pattern.files.length} files`);
|
|
466
|
+
}
|
|
467
|
+
lines.push("");
|
|
468
|
+
|
|
469
|
+
// File contexts
|
|
470
|
+
lines.push("### File Contexts");
|
|
471
|
+
const topContexts = analysis.contexts.slice(0, maxContexts);
|
|
472
|
+
for (const ctx of topContexts) {
|
|
473
|
+
lines.push(`- ${ctx.type}: ${ctx.count} usages`);
|
|
474
|
+
}
|
|
475
|
+
lines.push("");
|
|
476
|
+
|
|
477
|
+
// Sample code contexts (one per pattern)
|
|
478
|
+
lines.push("### Sample Usages");
|
|
479
|
+
for (const pattern of topPatterns.slice(0, 3)) {
|
|
480
|
+
if (pattern.sampleContexts[0]) {
|
|
481
|
+
lines.push("```tsx");
|
|
482
|
+
lines.push(pattern.sampleContexts[0].trim());
|
|
483
|
+
lines.push("```");
|
|
484
|
+
lines.push("");
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return lines.join("\n");
|
|
489
|
+
}
|