@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,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fragments scan - Zero-config segments.json generation from source code
|
|
3
|
+
*
|
|
4
|
+
* Automatically extracts component documentation by:
|
|
5
|
+
* 1. Discovering components from source files and barrel exports
|
|
6
|
+
* 2. Extracting props from TypeScript interfaces
|
|
7
|
+
* 3. Scanning codebase for usage patterns
|
|
8
|
+
* 4. Parsing Storybook stories for examples
|
|
9
|
+
* 5. Inferring component relationships from usage data
|
|
10
|
+
* 6. Generating complete segments.json without manual documentation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
14
|
+
import { resolve, join, dirname, relative } from "node:path";
|
|
15
|
+
import pc from "picocolors";
|
|
16
|
+
import {
|
|
17
|
+
BRAND,
|
|
18
|
+
type CompiledSegmentsFile,
|
|
19
|
+
type CompiledSegment,
|
|
20
|
+
type PropDefinition,
|
|
21
|
+
} from "../core/index.js";
|
|
22
|
+
import {
|
|
23
|
+
loadConfig,
|
|
24
|
+
discoverAllComponents,
|
|
25
|
+
type DiscoveredComponent,
|
|
26
|
+
} from "../core/node.js";
|
|
27
|
+
import {
|
|
28
|
+
scanCodebase,
|
|
29
|
+
extractPropsFromFile,
|
|
30
|
+
parseAllStories,
|
|
31
|
+
inferAllRelations,
|
|
32
|
+
generateComponentContext,
|
|
33
|
+
generateEnhancementSuggestions,
|
|
34
|
+
filterBoilerplate,
|
|
35
|
+
convertToSegmentProps,
|
|
36
|
+
type UsageAnalysis,
|
|
37
|
+
type ComponentRelation,
|
|
38
|
+
type PropsExtractionResult,
|
|
39
|
+
type ParsedStoryFile,
|
|
40
|
+
} from "../service/index.js";
|
|
41
|
+
|
|
42
|
+
export interface ScanOptions {
|
|
43
|
+
/** Path to config file */
|
|
44
|
+
config?: string;
|
|
45
|
+
/** Output file path (default: segments.json) */
|
|
46
|
+
output?: string;
|
|
47
|
+
/** Component patterns to scan */
|
|
48
|
+
componentPatterns?: string[];
|
|
49
|
+
/** Barrel export files to parse */
|
|
50
|
+
barrelFiles?: string[];
|
|
51
|
+
/** Directory to scan for usage patterns */
|
|
52
|
+
usageDir?: string;
|
|
53
|
+
/** Skip usage analysis */
|
|
54
|
+
skipUsage?: boolean;
|
|
55
|
+
/** Skip Storybook parsing */
|
|
56
|
+
skipStorybook?: boolean;
|
|
57
|
+
/** Verbose output */
|
|
58
|
+
verbose?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ScanResult {
|
|
62
|
+
success: boolean;
|
|
63
|
+
outputPath: string;
|
|
64
|
+
componentCount: number;
|
|
65
|
+
propsExtracted: number;
|
|
66
|
+
usagesFound: number;
|
|
67
|
+
relationsInferred: number;
|
|
68
|
+
storiesParsed: number;
|
|
69
|
+
errors: Array<{ component: string; error: string }>;
|
|
70
|
+
warnings: Array<{ component: string; warning: string }>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Scan codebase and generate segments.json directly from source
|
|
75
|
+
*/
|
|
76
|
+
export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
77
|
+
const startTime = Date.now();
|
|
78
|
+
const errors: Array<{ component: string; error: string }> = [];
|
|
79
|
+
const warnings: Array<{ component: string; warning: string }> = [];
|
|
80
|
+
|
|
81
|
+
// Load config or use defaults
|
|
82
|
+
let configDir: string;
|
|
83
|
+
let outputFile: string;
|
|
84
|
+
let componentPatterns: string[] | undefined;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const loaded = await loadConfig(options.config);
|
|
88
|
+
configDir = loaded.configDir;
|
|
89
|
+
outputFile = options.output || loaded.config.outFile || "segments.json";
|
|
90
|
+
componentPatterns = options.componentPatterns || loaded.config.components;
|
|
91
|
+
} catch {
|
|
92
|
+
// No config file, use defaults
|
|
93
|
+
configDir = process.cwd();
|
|
94
|
+
outputFile = options.output || "segments.json";
|
|
95
|
+
componentPatterns = options.componentPatterns;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(pc.cyan(`\n${BRAND.name} Scan\n`));
|
|
99
|
+
console.log(pc.dim("Zero-config segments.json generation from source code\n"));
|
|
100
|
+
|
|
101
|
+
// Phase 1: Discover components
|
|
102
|
+
console.log(pc.dim("Phase 1: Discovering components..."));
|
|
103
|
+
const components = await discoverAllComponents(configDir, {
|
|
104
|
+
patterns: componentPatterns,
|
|
105
|
+
exclude: ["**/*.test.*", "**/*.spec.*", "**/__tests__/**"],
|
|
106
|
+
barrelFiles: options.barrelFiles,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (components.length === 0) {
|
|
110
|
+
console.log(pc.yellow("No components found. Check your patterns or config."));
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
outputPath: resolve(configDir, outputFile),
|
|
114
|
+
componentCount: 0,
|
|
115
|
+
propsExtracted: 0,
|
|
116
|
+
usagesFound: 0,
|
|
117
|
+
relationsInferred: 0,
|
|
118
|
+
storiesParsed: 0,
|
|
119
|
+
errors: [{ component: "*", error: "No components found" }],
|
|
120
|
+
warnings: [],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(pc.green(` Found ${components.length} components`));
|
|
125
|
+
if (options.verbose) {
|
|
126
|
+
for (const comp of components.slice(0, 10)) {
|
|
127
|
+
console.log(pc.dim(` - ${comp.name}: ${comp.relativePath}`));
|
|
128
|
+
}
|
|
129
|
+
if (components.length > 10) {
|
|
130
|
+
console.log(pc.dim(` ... and ${components.length - 10} more`));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Phase 2: Extract props from TypeScript
|
|
135
|
+
console.log(pc.dim("\nPhase 2: Extracting props from TypeScript..."));
|
|
136
|
+
const propsMap = new Map<string, ReturnType<typeof convertToSegmentProps>>();
|
|
137
|
+
const propsResults = new Map<string, PropsExtractionResult>();
|
|
138
|
+
let propsExtracted = 0;
|
|
139
|
+
|
|
140
|
+
for (const comp of components) {
|
|
141
|
+
try {
|
|
142
|
+
const extraction = await extractPropsFromFile(comp.sourcePath, {
|
|
143
|
+
propsTypeName: `${comp.name}Props`,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
propsResults.set(comp.name, extraction);
|
|
147
|
+
|
|
148
|
+
if (extraction.success && extraction.props.length > 0) {
|
|
149
|
+
propsMap.set(comp.name, convertToSegmentProps(extraction.props));
|
|
150
|
+
propsExtracted++;
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
if (options.verbose) {
|
|
154
|
+
warnings.push({
|
|
155
|
+
component: comp.name,
|
|
156
|
+
warning: `Props extraction failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log(pc.green(` Extracted props for ${propsExtracted} components`));
|
|
163
|
+
|
|
164
|
+
// Phase 3: Scan for usage patterns
|
|
165
|
+
let usageAnalysis: UsageAnalysis | undefined;
|
|
166
|
+
let usagesFound = 0;
|
|
167
|
+
let allRelations = new Map<string, ComponentRelation[]>();
|
|
168
|
+
|
|
169
|
+
if (!options.skipUsage) {
|
|
170
|
+
console.log(pc.dim("\nPhase 3: Scanning for usage patterns..."));
|
|
171
|
+
const usageDir = options.usageDir || configDir;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
// Get component names for filtering
|
|
175
|
+
const componentNames = components.map((c) => c.name);
|
|
176
|
+
|
|
177
|
+
usageAnalysis = await scanCodebase({
|
|
178
|
+
rootDir: usageDir,
|
|
179
|
+
include: ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"],
|
|
180
|
+
exclude: [
|
|
181
|
+
"**/node_modules/**",
|
|
182
|
+
"**/dist/**",
|
|
183
|
+
"**/*.stories.*",
|
|
184
|
+
"**/*.test.*",
|
|
185
|
+
"**/*.spec.*",
|
|
186
|
+
],
|
|
187
|
+
componentNames,
|
|
188
|
+
useCache: true,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Count total usages across all components
|
|
192
|
+
usagesFound = Object.values(usageAnalysis.components).reduce(
|
|
193
|
+
(sum, comp) => sum + comp.totalUsages,
|
|
194
|
+
0
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Infer relations
|
|
198
|
+
allRelations = inferAllRelations(usageAnalysis);
|
|
199
|
+
console.log(pc.green(` Found ${usagesFound} usages across ${usageAnalysis.totalFiles} files`));
|
|
200
|
+
console.log(pc.green(` Inferred relations for ${allRelations.size} components`));
|
|
201
|
+
} catch (e) {
|
|
202
|
+
warnings.push({
|
|
203
|
+
component: "*",
|
|
204
|
+
warning: `Usage scanning failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
205
|
+
});
|
|
206
|
+
console.log(pc.yellow(` Usage scanning failed: ${e instanceof Error ? e.message : "unknown error"}`));
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
console.log(pc.dim("\nPhase 3: Skipping usage analysis"));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Phase 4: Parse Storybook stories
|
|
213
|
+
const storiesMap = new Map<string, ParsedStoryFile>();
|
|
214
|
+
let storiesParsed = 0;
|
|
215
|
+
|
|
216
|
+
if (!options.skipStorybook) {
|
|
217
|
+
console.log(pc.dim("\nPhase 4: Parsing Storybook stories..."));
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const allStories = await parseAllStories(configDir);
|
|
221
|
+
|
|
222
|
+
for (const [name, stories] of Object.entries(allStories)) {
|
|
223
|
+
if (stories && stories.stories.length > 0) {
|
|
224
|
+
storiesMap.set(name, stories);
|
|
225
|
+
storiesParsed++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log(pc.green(` Parsed stories for ${storiesParsed} components`));
|
|
230
|
+
} catch (e) {
|
|
231
|
+
warnings.push({
|
|
232
|
+
component: "*",
|
|
233
|
+
warning: `Storybook parsing failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
234
|
+
});
|
|
235
|
+
console.log(pc.yellow(` Storybook parsing failed: ${e instanceof Error ? e.message : "unknown error"}`));
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
console.log(pc.dim("\nPhase 4: Skipping Storybook parsing"));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Phase 5: Generate segments
|
|
242
|
+
console.log(pc.dim("\nPhase 5: Generating segments..."));
|
|
243
|
+
const segments: Record<string, CompiledSegment> = {};
|
|
244
|
+
|
|
245
|
+
for (const comp of components) {
|
|
246
|
+
try {
|
|
247
|
+
const segment = generateSegmentFromData(
|
|
248
|
+
comp,
|
|
249
|
+
configDir,
|
|
250
|
+
propsMap.get(comp.name),
|
|
251
|
+
usageAnalysis?.components[comp.name],
|
|
252
|
+
allRelations.get(comp.name),
|
|
253
|
+
storiesMap.get(comp.name)
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
segments[comp.name] = segment;
|
|
257
|
+
} catch (e) {
|
|
258
|
+
errors.push({
|
|
259
|
+
component: comp.name,
|
|
260
|
+
error: e instanceof Error ? e.message : String(e),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Write output
|
|
266
|
+
const outputPath = resolve(configDir, outputFile);
|
|
267
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
268
|
+
|
|
269
|
+
const output: CompiledSegmentsFile = {
|
|
270
|
+
version: "1.0.0",
|
|
271
|
+
generatedAt: new Date().toISOString(),
|
|
272
|
+
segments,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
await writeFile(outputPath, JSON.stringify(output, null, 2));
|
|
276
|
+
|
|
277
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
278
|
+
|
|
279
|
+
// Summary
|
|
280
|
+
console.log(pc.dim("\n────────────────────────────────────────"));
|
|
281
|
+
console.log(pc.green(`\n✓ Generated segments.json in ${elapsed}s`));
|
|
282
|
+
console.log(pc.dim(` Output: ${relative(process.cwd(), outputPath)}`));
|
|
283
|
+
console.log(pc.dim(` Components: ${Object.keys(segments).length}`));
|
|
284
|
+
console.log(pc.dim(` Props extracted: ${propsExtracted}`));
|
|
285
|
+
console.log(pc.dim(` Usages found: ${usagesFound}`));
|
|
286
|
+
console.log(pc.dim(` Relations inferred: ${allRelations.size}`));
|
|
287
|
+
console.log(pc.dim(` Stories parsed: ${storiesParsed}`));
|
|
288
|
+
|
|
289
|
+
if (warnings.length > 0) {
|
|
290
|
+
console.log(pc.yellow(`\n ${warnings.length} warning(s)`));
|
|
291
|
+
if (options.verbose) {
|
|
292
|
+
for (const w of warnings) {
|
|
293
|
+
console.log(pc.dim(` ${w.component}: ${w.warning}`));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (errors.length > 0) {
|
|
299
|
+
console.log(pc.red(`\n ${errors.length} error(s)`));
|
|
300
|
+
for (const e of errors) {
|
|
301
|
+
console.log(pc.dim(` ${e.component}: ${e.error}`));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
console.log();
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
success: errors.length === 0,
|
|
309
|
+
outputPath,
|
|
310
|
+
componentCount: Object.keys(segments).length,
|
|
311
|
+
propsExtracted,
|
|
312
|
+
usagesFound,
|
|
313
|
+
relationsInferred: allRelations.size,
|
|
314
|
+
storiesParsed,
|
|
315
|
+
errors,
|
|
316
|
+
warnings,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Generate a CompiledSegment from extracted data
|
|
322
|
+
*/
|
|
323
|
+
function generateSegmentFromData(
|
|
324
|
+
comp: DiscoveredComponent,
|
|
325
|
+
configDir: string,
|
|
326
|
+
props: Record<string, PropDefinition> | undefined,
|
|
327
|
+
usageAnalysis: UsageAnalysis["components"][string] | undefined,
|
|
328
|
+
relations: ComponentRelation[] | undefined,
|
|
329
|
+
storyFile: ParsedStoryFile | undefined
|
|
330
|
+
): CompiledSegment {
|
|
331
|
+
// Generate context for AI suggestions
|
|
332
|
+
const context = generateComponentContext(
|
|
333
|
+
comp.name,
|
|
334
|
+
usageAnalysis,
|
|
335
|
+
undefined, // No extracted docs yet
|
|
336
|
+
storyFile,
|
|
337
|
+
undefined, // No props extraction result (we have the converted props already)
|
|
338
|
+
relations
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// Generate enhancement suggestions from context
|
|
342
|
+
const suggestions = generateEnhancementSuggestions(context);
|
|
343
|
+
|
|
344
|
+
// Filter boilerplate from suggestions
|
|
345
|
+
const when = filterBoilerplate(suggestions.suggestions.when);
|
|
346
|
+
const whenNot = filterBoilerplate(suggestions.suggestions.whenNot);
|
|
347
|
+
|
|
348
|
+
// Infer category from path
|
|
349
|
+
const category = inferCategory(comp.relativePath);
|
|
350
|
+
|
|
351
|
+
// Infer status from path
|
|
352
|
+
const status = inferStatus(comp.relativePath);
|
|
353
|
+
|
|
354
|
+
// Build variants from stories
|
|
355
|
+
const variants: CompiledSegment["variants"] = [];
|
|
356
|
+
if (storyFile?.stories) {
|
|
357
|
+
for (const story of storyFile.stories) {
|
|
358
|
+
variants.push({
|
|
359
|
+
name: story.name,
|
|
360
|
+
description: story.description || `${story.displayName} variant`,
|
|
361
|
+
code: story.code,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Build relations
|
|
367
|
+
const compiledRelations: CompiledSegment["relations"] = [];
|
|
368
|
+
if (relations && relations.length > 0) {
|
|
369
|
+
for (const rel of relations.slice(0, 10)) {
|
|
370
|
+
compiledRelations.push({
|
|
371
|
+
component: rel.component,
|
|
372
|
+
relationship: rel.relationship,
|
|
373
|
+
note: `Appears together ${rel.frequency} times`,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Generate description
|
|
379
|
+
const description = generateDescription(comp.name, props, usageAnalysis);
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
filePath: comp.relativePath,
|
|
383
|
+
meta: {
|
|
384
|
+
name: comp.name,
|
|
385
|
+
description,
|
|
386
|
+
category,
|
|
387
|
+
status,
|
|
388
|
+
},
|
|
389
|
+
usage: {
|
|
390
|
+
when: when.length > 0 ? when : [`Use ${comp.name} for its intended purpose`],
|
|
391
|
+
whenNot: whenNot,
|
|
392
|
+
},
|
|
393
|
+
props: props || {},
|
|
394
|
+
relations: compiledRelations.length > 0 ? compiledRelations : undefined,
|
|
395
|
+
variants,
|
|
396
|
+
_generated: {
|
|
397
|
+
source: "ai",
|
|
398
|
+
sourceFile: comp.relativePath,
|
|
399
|
+
confidence: calculateConfidence(props, usageAnalysis, storyFile),
|
|
400
|
+
timestamp: new Date().toISOString(),
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Infer category from file path
|
|
407
|
+
*/
|
|
408
|
+
function inferCategory(relativePath: string): string {
|
|
409
|
+
const parts = relativePath.toLowerCase().split("/");
|
|
410
|
+
|
|
411
|
+
// Common category patterns
|
|
412
|
+
const categoryPatterns: Record<string, string[]> = {
|
|
413
|
+
"Actions": ["button", "action", "cta"],
|
|
414
|
+
"Forms": ["form", "input", "select", "checkbox", "radio", "textarea", "field"],
|
|
415
|
+
"Layout": ["layout", "container", "grid", "flex", "stack", "box", "divider", "spacer"],
|
|
416
|
+
"Navigation": ["nav", "menu", "breadcrumb", "tab", "link", "pagination"],
|
|
417
|
+
"Feedback": ["alert", "toast", "notification", "message", "badge", "indicator", "progress", "spinner", "loading"],
|
|
418
|
+
"Data Display": ["table", "list", "card", "avatar", "stat", "timeline", "tree"],
|
|
419
|
+
"Overlays": ["modal", "dialog", "drawer", "popover", "tooltip", "dropdown"],
|
|
420
|
+
"Typography": ["text", "heading", "title", "label", "paragraph"],
|
|
421
|
+
"Media": ["image", "video", "icon", "avatar"],
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
for (const [category, patterns] of Object.entries(categoryPatterns)) {
|
|
425
|
+
for (const pattern of patterns) {
|
|
426
|
+
if (parts.some((part) => part.includes(pattern))) {
|
|
427
|
+
return category;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Check path structure for category
|
|
433
|
+
const componentIdx = parts.findIndex((p) => p === "components");
|
|
434
|
+
if (componentIdx !== -1 && parts.length > componentIdx + 1) {
|
|
435
|
+
const categoryPart = parts[componentIdx + 1];
|
|
436
|
+
return categoryPart.charAt(0).toUpperCase() + categoryPart.slice(1);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return "Components";
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Infer status from file path
|
|
444
|
+
*/
|
|
445
|
+
function inferStatus(
|
|
446
|
+
relativePath: string
|
|
447
|
+
): "stable" | "beta" | "experimental" | "deprecated" {
|
|
448
|
+
const lowerPath = relativePath.toLowerCase();
|
|
449
|
+
|
|
450
|
+
if (lowerPath.includes("/experimental/") || lowerPath.includes("/labs/")) {
|
|
451
|
+
return "experimental";
|
|
452
|
+
}
|
|
453
|
+
if (lowerPath.includes("/beta/")) {
|
|
454
|
+
return "beta";
|
|
455
|
+
}
|
|
456
|
+
if (lowerPath.includes("/deprecated/") || lowerPath.includes("/legacy/")) {
|
|
457
|
+
return "deprecated";
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return "stable";
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Generate component description
|
|
465
|
+
*/
|
|
466
|
+
function generateDescription(
|
|
467
|
+
name: string,
|
|
468
|
+
props: Record<string, PropDefinition> | undefined,
|
|
469
|
+
usageAnalysis: UsageAnalysis["components"][string] | undefined
|
|
470
|
+
): string {
|
|
471
|
+
// Convert name to readable form
|
|
472
|
+
const words = name
|
|
473
|
+
.replace(/([A-Z])/g, " $1")
|
|
474
|
+
.trim()
|
|
475
|
+
.toLowerCase();
|
|
476
|
+
|
|
477
|
+
// Check props for hints
|
|
478
|
+
const hasOnClick = props && ("onClick" in props || "onPress" in props);
|
|
479
|
+
const hasValue = props && ("value" in props || "defaultValue" in props);
|
|
480
|
+
const hasChildren = props && "children" in props;
|
|
481
|
+
const hasHref = props && "href" in props;
|
|
482
|
+
|
|
483
|
+
// Check usage contexts
|
|
484
|
+
const topContext = usageAnalysis?.contexts[0]?.type;
|
|
485
|
+
|
|
486
|
+
if (hasHref) {
|
|
487
|
+
return `Navigational ${words} for linking to other pages or resources`;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (hasOnClick && !hasValue) {
|
|
491
|
+
return `Interactive ${words} for triggering actions`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (hasValue) {
|
|
495
|
+
return `Form ${words} for user input`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (topContext && topContext !== "unknown") {
|
|
499
|
+
return `${words.charAt(0).toUpperCase() + words.slice(1)} commonly used in ${topContext} contexts`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (hasChildren) {
|
|
503
|
+
return `Container ${words} for composing UI`;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return `${words.charAt(0).toUpperCase() + words.slice(1)} component`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Calculate confidence score based on available data
|
|
511
|
+
*/
|
|
512
|
+
function calculateConfidence(
|
|
513
|
+
props: Record<string, PropDefinition> | undefined,
|
|
514
|
+
usageAnalysis: UsageAnalysis["components"][string] | undefined,
|
|
515
|
+
storyFile: ParsedStoryFile | undefined
|
|
516
|
+
): number {
|
|
517
|
+
let confidence = 0;
|
|
518
|
+
|
|
519
|
+
// Props give +30 confidence
|
|
520
|
+
if (props && Object.keys(props).length > 0) {
|
|
521
|
+
confidence += 30;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Usage data gives up to +40 confidence
|
|
525
|
+
if (usageAnalysis) {
|
|
526
|
+
if (usageAnalysis.totalUsages > 10) confidence += 40;
|
|
527
|
+
else if (usageAnalysis.totalUsages > 5) confidence += 30;
|
|
528
|
+
else if (usageAnalysis.totalUsages > 0) confidence += 20;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Stories give +30 confidence
|
|
532
|
+
if (storyFile && storyFile.stories.length > 0) {
|
|
533
|
+
confidence += 30;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return Math.min(confidence, 100);
|
|
537
|
+
}
|