@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,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fragments generate - AI-assisted fragment generation
|
|
3
|
+
*
|
|
4
|
+
* Analyzes component source code and generates fragment files with:
|
|
5
|
+
* - Extracted props from TypeScript
|
|
6
|
+
* - Inferred usage patterns from story names
|
|
7
|
+
* - Generated description
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFile, writeFile, mkdir, access } from "node:fs/promises";
|
|
11
|
+
import { resolve, join, basename, dirname, relative } from "node:path";
|
|
12
|
+
import pc from "picocolors";
|
|
13
|
+
import fg from "fast-glob";
|
|
14
|
+
import { BRAND, type Fragment } from "../core/index.js";
|
|
15
|
+
import { extractPropsFromFile } from "../core/node.js";
|
|
16
|
+
|
|
17
|
+
export interface GenerateOptions {
|
|
18
|
+
/** Project root directory */
|
|
19
|
+
projectRoot?: string;
|
|
20
|
+
/** Specific component name to generate (optional) */
|
|
21
|
+
component?: string;
|
|
22
|
+
/** Overwrite existing fragment files */
|
|
23
|
+
force?: boolean;
|
|
24
|
+
/** Pattern for component files */
|
|
25
|
+
componentPattern?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface GenerateResult {
|
|
29
|
+
success: boolean;
|
|
30
|
+
generated: Array<{ name: string; path: string }>;
|
|
31
|
+
skipped: Array<{ name: string; reason: string }>;
|
|
32
|
+
errors: Array<{ name: string; error: string }>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate fragment files from component source code
|
|
37
|
+
*/
|
|
38
|
+
export async function generate(options: GenerateOptions = {}): Promise<GenerateResult> {
|
|
39
|
+
const projectRoot = resolve(options.projectRoot || process.cwd());
|
|
40
|
+
const generated: Array<{ name: string; path: string }> = [];
|
|
41
|
+
const skipped: Array<{ name: string; reason: string }> = [];
|
|
42
|
+
const errors: Array<{ name: string; error: string }> = [];
|
|
43
|
+
|
|
44
|
+
console.log(pc.cyan(`\n${BRAND.name} Generate\n`));
|
|
45
|
+
|
|
46
|
+
// Ensure .fragments/components directory exists
|
|
47
|
+
const fragmentsDir = join(projectRoot, BRAND.dataDir, BRAND.componentsDir);
|
|
48
|
+
await mkdir(fragmentsDir, { recursive: true });
|
|
49
|
+
|
|
50
|
+
// Find component files
|
|
51
|
+
const componentPattern =
|
|
52
|
+
options.componentPattern ||
|
|
53
|
+
"src/components/**/*.tsx";
|
|
54
|
+
|
|
55
|
+
const componentFiles = await fg(componentPattern, {
|
|
56
|
+
cwd: projectRoot,
|
|
57
|
+
ignore: [
|
|
58
|
+
"**/node_modules/**",
|
|
59
|
+
"**/*.stories.*",
|
|
60
|
+
"**/*.segment.*",
|
|
61
|
+
"**/*.test.*",
|
|
62
|
+
"**/*.spec.*",
|
|
63
|
+
"**/*.d.ts",
|
|
64
|
+
"**/index.tsx", // Often just re-exports
|
|
65
|
+
],
|
|
66
|
+
absolute: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Find story files for pattern inference
|
|
70
|
+
const storyFiles = await fg(["src/**/*.stories.tsx", "src/**/*.stories.ts"], {
|
|
71
|
+
cwd: projectRoot,
|
|
72
|
+
ignore: ["**/node_modules/**"],
|
|
73
|
+
absolute: true,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Build story map for pattern inference
|
|
77
|
+
const storyMap = new Map<string, string[]>();
|
|
78
|
+
for (const storyFile of storyFiles) {
|
|
79
|
+
try {
|
|
80
|
+
const content = await readFile(storyFile, "utf-8");
|
|
81
|
+
const componentName = extractComponentNameFromStory(content, storyFile);
|
|
82
|
+
if (componentName) {
|
|
83
|
+
const storyNames = extractStoryNames(content);
|
|
84
|
+
storyMap.set(componentName, storyNames);
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// Ignore parsing errors
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(pc.dim(`Found ${componentFiles.length} component files\n`));
|
|
92
|
+
|
|
93
|
+
// Process each component file
|
|
94
|
+
for (const filePath of componentFiles) {
|
|
95
|
+
try {
|
|
96
|
+
const extracted = extractPropsFromFile(filePath);
|
|
97
|
+
|
|
98
|
+
if (!extracted || !extracted.componentName) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const componentName = extracted.componentName;
|
|
103
|
+
|
|
104
|
+
// Filter by component name if specified
|
|
105
|
+
if (options.component && componentName !== options.component) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if fragment already exists
|
|
110
|
+
const fragmentPath = join(fragmentsDir, `${componentName}${BRAND.fileExtension}`);
|
|
111
|
+
let fragmentExists = false;
|
|
112
|
+
try {
|
|
113
|
+
await access(fragmentPath);
|
|
114
|
+
fragmentExists = true;
|
|
115
|
+
} catch {
|
|
116
|
+
// Fragment doesn't exist
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (fragmentExists && !options.force) {
|
|
120
|
+
skipped.push({ name: componentName, reason: "Fragment already exists" });
|
|
121
|
+
console.log(pc.dim(` Skipping ${componentName} (fragment exists)`));
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Generate fragment from analysis
|
|
126
|
+
const fragment = generateFragmentFromComponent(
|
|
127
|
+
componentName,
|
|
128
|
+
extracted,
|
|
129
|
+
filePath,
|
|
130
|
+
storyMap.get(componentName) || []
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Write fragment file
|
|
134
|
+
await writeFile(fragmentPath, JSON.stringify(fragment, null, 2), "utf-8");
|
|
135
|
+
generated.push({ name: componentName, path: relative(projectRoot, fragmentPath) });
|
|
136
|
+
console.log(pc.green(` ✓ Generated ${componentName}${BRAND.fileExtension}`));
|
|
137
|
+
} catch (e) {
|
|
138
|
+
const fileName = basename(filePath);
|
|
139
|
+
errors.push({
|
|
140
|
+
name: fileName,
|
|
141
|
+
error: e instanceof Error ? e.message : String(e),
|
|
142
|
+
});
|
|
143
|
+
console.log(pc.red(` ✗ Failed: ${fileName}`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Summary
|
|
148
|
+
console.log();
|
|
149
|
+
if (generated.length > 0) {
|
|
150
|
+
console.log(pc.green(`✓ Generated ${generated.length} fragment(s)`));
|
|
151
|
+
}
|
|
152
|
+
if (skipped.length > 0) {
|
|
153
|
+
console.log(pc.dim(` Skipped ${skipped.length} (use --force to overwrite)`));
|
|
154
|
+
}
|
|
155
|
+
if (errors.length > 0) {
|
|
156
|
+
console.log(pc.yellow(` ${errors.length} error(s)`));
|
|
157
|
+
}
|
|
158
|
+
console.log();
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
success: errors.length === 0,
|
|
162
|
+
generated,
|
|
163
|
+
skipped,
|
|
164
|
+
errors,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Generate a Fragment from extracted component data
|
|
170
|
+
*/
|
|
171
|
+
function generateFragmentFromComponent(
|
|
172
|
+
componentName: string,
|
|
173
|
+
extracted: ReturnType<typeof extractPropsFromFile> & { componentName: string },
|
|
174
|
+
filePath: string,
|
|
175
|
+
storyNames: string[]
|
|
176
|
+
): Fragment {
|
|
177
|
+
// Infer usage from story names
|
|
178
|
+
const whenToUse = inferUsageFromStories(storyNames);
|
|
179
|
+
|
|
180
|
+
// Generate description from component name
|
|
181
|
+
const description = generateDescription(componentName, extracted.props);
|
|
182
|
+
|
|
183
|
+
// Infer accessibility from props
|
|
184
|
+
const accessibility = inferAccessibility(extracted.props);
|
|
185
|
+
|
|
186
|
+
// Infer status from file path
|
|
187
|
+
const status = inferStatus(filePath);
|
|
188
|
+
|
|
189
|
+
const fragment: Fragment = {
|
|
190
|
+
$schema: "https://fragments.dev/schema/v1.json",
|
|
191
|
+
name: componentName,
|
|
192
|
+
description,
|
|
193
|
+
usage: {
|
|
194
|
+
when: whenToUse,
|
|
195
|
+
doNot: [],
|
|
196
|
+
},
|
|
197
|
+
meta: {
|
|
198
|
+
status,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Add accessibility if any was inferred
|
|
203
|
+
if (accessibility.role || (accessibility.requirements && accessibility.requirements.length > 0)) {
|
|
204
|
+
fragment.accessibility = accessibility;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return fragment;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Extract component name from story file
|
|
212
|
+
*/
|
|
213
|
+
function extractComponentNameFromStory(content: string, filePath: string): string | null {
|
|
214
|
+
// Try to extract from title in default export
|
|
215
|
+
// e.g., export default { title: 'Components/Button' }
|
|
216
|
+
const titleMatch = content.match(/title:\s*['"](?:[^'"]+\/)?([^'"]+)['"]/);
|
|
217
|
+
if (titleMatch) {
|
|
218
|
+
return titleMatch[1];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Try to extract from file name
|
|
222
|
+
const fileName = basename(filePath);
|
|
223
|
+
const componentName = fileName.replace(/\.stories\.(tsx?|jsx?)$/, "");
|
|
224
|
+
if (/^[A-Z]/.test(componentName)) {
|
|
225
|
+
return componentName;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Extract story names from story file content
|
|
233
|
+
*/
|
|
234
|
+
function extractStoryNames(content: string): string[] {
|
|
235
|
+
const names: string[] = [];
|
|
236
|
+
|
|
237
|
+
// Match named exports that look like stories
|
|
238
|
+
// e.g., export const Primary = ...
|
|
239
|
+
const exportMatches = content.matchAll(
|
|
240
|
+
/export\s+const\s+([A-Z][a-zA-Z0-9]*)\s*[=:]/g
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
for (const match of exportMatches) {
|
|
244
|
+
const name = match[1];
|
|
245
|
+
if (name !== "default" && !name.endsWith("Args") && !name.endsWith("Meta")) {
|
|
246
|
+
names.push(name);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return names;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Infer usage scenarios from story names
|
|
255
|
+
*/
|
|
256
|
+
function inferUsageFromStories(storyNames: string[]): string[] {
|
|
257
|
+
const usage: string[] = [];
|
|
258
|
+
|
|
259
|
+
for (const name of storyNames) {
|
|
260
|
+
// Convert PascalCase to sentence
|
|
261
|
+
const sentence = name
|
|
262
|
+
.replace(/([A-Z])/g, " $1")
|
|
263
|
+
.trim()
|
|
264
|
+
.toLowerCase();
|
|
265
|
+
|
|
266
|
+
// Skip generic names
|
|
267
|
+
if (
|
|
268
|
+
["default", "primary", "basic", "example", "playground"].includes(
|
|
269
|
+
sentence.toLowerCase()
|
|
270
|
+
)
|
|
271
|
+
) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Generate usage from story name
|
|
276
|
+
if (sentence.includes("loading")) {
|
|
277
|
+
usage.push("Showing loading states");
|
|
278
|
+
} else if (sentence.includes("disabled")) {
|
|
279
|
+
usage.push("Preventing user interaction");
|
|
280
|
+
} else if (sentence.includes("error")) {
|
|
281
|
+
usage.push("Displaying error states");
|
|
282
|
+
} else if (sentence.includes("success")) {
|
|
283
|
+
usage.push("Showing success feedback");
|
|
284
|
+
} else if (sentence.includes("empty")) {
|
|
285
|
+
usage.push("Handling empty states");
|
|
286
|
+
} else if (sentence.includes("with")) {
|
|
287
|
+
// "WithIcon" -> "Adding icons"
|
|
288
|
+
const withPart = sentence.replace("with ", "");
|
|
289
|
+
usage.push(`Displaying with ${withPart}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return usage;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Generate description from component name and props
|
|
298
|
+
*/
|
|
299
|
+
function generateDescription(
|
|
300
|
+
componentName: string,
|
|
301
|
+
props: Record<string, unknown>
|
|
302
|
+
): string {
|
|
303
|
+
// Convert PascalCase to words
|
|
304
|
+
const words = componentName
|
|
305
|
+
.replace(/([A-Z])/g, " $1")
|
|
306
|
+
.trim()
|
|
307
|
+
.toLowerCase();
|
|
308
|
+
|
|
309
|
+
// Detect component type from name or props
|
|
310
|
+
const hasOnClick = "onClick" in props || "onPress" in props;
|
|
311
|
+
const hasValue = "value" in props || "defaultValue" in props;
|
|
312
|
+
const hasChildren = "children" in props;
|
|
313
|
+
|
|
314
|
+
if (hasOnClick && !hasValue) {
|
|
315
|
+
return `Interactive ${words} element for triggering actions`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (hasValue) {
|
|
319
|
+
return `Form ${words} for user input`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (hasChildren) {
|
|
323
|
+
return `Container ${words} for grouping content`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return `${words.charAt(0).toUpperCase() + words.slice(1)} component`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Infer accessibility from props
|
|
331
|
+
*/
|
|
332
|
+
function inferAccessibility(props: Record<string, unknown>): {
|
|
333
|
+
role?: string;
|
|
334
|
+
requirements?: string[];
|
|
335
|
+
} {
|
|
336
|
+
const accessibility: { role?: string; requirements?: string[] } = {};
|
|
337
|
+
|
|
338
|
+
const hasOnClick = "onClick" in props || "onPress" in props;
|
|
339
|
+
const hasAriaLabel = "ariaLabel" in props || "aria-label" in props;
|
|
340
|
+
const hasRole = "role" in props;
|
|
341
|
+
const hasDisabled = "disabled" in props;
|
|
342
|
+
const hasHref = "href" in props;
|
|
343
|
+
|
|
344
|
+
// Infer role
|
|
345
|
+
if (hasOnClick && !hasHref) {
|
|
346
|
+
accessibility.role = "button";
|
|
347
|
+
} else if (hasHref) {
|
|
348
|
+
accessibility.role = "link";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Infer requirements
|
|
352
|
+
const requirements: string[] = [];
|
|
353
|
+
|
|
354
|
+
if (hasOnClick && !hasAriaLabel) {
|
|
355
|
+
requirements.push("Should have visible text or aria-label");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (hasDisabled) {
|
|
359
|
+
requirements.push("Disabled state should be conveyed to assistive technology");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (requirements.length > 0) {
|
|
363
|
+
accessibility.requirements = requirements;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return accessibility;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Infer status from file path
|
|
371
|
+
*/
|
|
372
|
+
function inferStatus(
|
|
373
|
+
filePath: string
|
|
374
|
+
): "draft" | "experimental" | "beta" | "stable" | "deprecated" {
|
|
375
|
+
const lowerPath = filePath.toLowerCase();
|
|
376
|
+
|
|
377
|
+
if (lowerPath.includes("/experimental/") || lowerPath.includes("/labs/")) {
|
|
378
|
+
return "experimental";
|
|
379
|
+
}
|
|
380
|
+
if (lowerPath.includes("/beta/")) {
|
|
381
|
+
return "beta";
|
|
382
|
+
}
|
|
383
|
+
if (lowerPath.includes("/deprecated/") || lowerPath.includes("/legacy/")) {
|
|
384
|
+
return "deprecated";
|
|
385
|
+
}
|
|
386
|
+
if (lowerPath.includes("/draft/") || lowerPath.includes("/wip/")) {
|
|
387
|
+
return "draft";
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return "stable";
|
|
391
|
+
}
|