@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,337 @@
|
|
|
1
|
+
import { resolve, dirname, basename } from 'node:path';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import fg from 'fast-glob';
|
|
5
|
+
import type { SegmentsConfig } from './types.js';
|
|
6
|
+
import { BRAND } from './constants.js';
|
|
7
|
+
|
|
8
|
+
export interface DiscoveredFile {
|
|
9
|
+
/** Absolute path to the file */
|
|
10
|
+
absolutePath: string;
|
|
11
|
+
/** Path relative to config directory */
|
|
12
|
+
relativePath: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Discovered component with source file information
|
|
17
|
+
*/
|
|
18
|
+
export interface DiscoveredComponent {
|
|
19
|
+
/** Component name (e.g., "Button") */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Absolute path to the component source file */
|
|
22
|
+
sourcePath: string;
|
|
23
|
+
/** Path relative to config directory */
|
|
24
|
+
relativePath: string;
|
|
25
|
+
/** Path to storybook file if found */
|
|
26
|
+
storyPath?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Discover recipe files (*.recipe.ts) under the config directory
|
|
31
|
+
*/
|
|
32
|
+
export async function discoverRecipeFiles(
|
|
33
|
+
configDir: string,
|
|
34
|
+
exclude?: string[]
|
|
35
|
+
): Promise<DiscoveredFile[]> {
|
|
36
|
+
const pattern = `**/*${BRAND.recipeFileExtension}`;
|
|
37
|
+
const files = await fg(pattern, {
|
|
38
|
+
cwd: configDir,
|
|
39
|
+
ignore: exclude ?? ['**/node_modules/**', '**/dist/**'],
|
|
40
|
+
absolute: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return files.map((relativePath) => ({
|
|
44
|
+
relativePath,
|
|
45
|
+
absolutePath: resolve(configDir, relativePath),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Discover segment files matching the config patterns
|
|
51
|
+
*/
|
|
52
|
+
export async function discoverSegmentFiles(
|
|
53
|
+
config: SegmentsConfig,
|
|
54
|
+
configDir: string
|
|
55
|
+
): Promise<DiscoveredFile[]> {
|
|
56
|
+
const files = await fg(config.include, {
|
|
57
|
+
cwd: configDir,
|
|
58
|
+
ignore: config.exclude ?? [],
|
|
59
|
+
absolute: false,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return files.map((relativePath) => ({
|
|
63
|
+
relativePath,
|
|
64
|
+
absolutePath: resolve(configDir, relativePath),
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Discover component files for coverage validation
|
|
70
|
+
*/
|
|
71
|
+
export async function discoverComponentFiles(
|
|
72
|
+
config: SegmentsConfig,
|
|
73
|
+
configDir: string
|
|
74
|
+
): Promise<DiscoveredFile[]> {
|
|
75
|
+
if (!config.components || config.components.length === 0) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const files = await fg(config.components, {
|
|
80
|
+
cwd: configDir,
|
|
81
|
+
ignore: [
|
|
82
|
+
...(config.exclude ?? []),
|
|
83
|
+
// Exclude segment files themselves
|
|
84
|
+
...config.include,
|
|
85
|
+
// Exclude test files
|
|
86
|
+
'**/*.test.*',
|
|
87
|
+
'**/*.spec.*',
|
|
88
|
+
'**/__tests__/**',
|
|
89
|
+
],
|
|
90
|
+
absolute: false,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return files.map((relativePath) => ({
|
|
94
|
+
relativePath,
|
|
95
|
+
absolutePath: resolve(configDir, relativePath),
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Extract component name from file path
|
|
101
|
+
*/
|
|
102
|
+
export function extractComponentName(filePath: string): string {
|
|
103
|
+
// Handle index.tsx files - use parent directory name
|
|
104
|
+
const parts = filePath.replace(/\\/g, '/').split('/');
|
|
105
|
+
const fileName = parts[parts.length - 1];
|
|
106
|
+
|
|
107
|
+
if (fileName === 'index.tsx' || fileName === 'index.ts') {
|
|
108
|
+
return parts[parts.length - 2] ?? 'Unknown';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Remove extension
|
|
112
|
+
return fileName.replace(/\.(tsx?|jsx?)$/, '');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Default patterns for component discovery
|
|
117
|
+
*/
|
|
118
|
+
const DEFAULT_COMPONENT_PATTERNS = [
|
|
119
|
+
'src/components/**/*.tsx',
|
|
120
|
+
'src/components/**/index.tsx',
|
|
121
|
+
'components/**/*.tsx',
|
|
122
|
+
'lib/components/**/*.tsx',
|
|
123
|
+
'packages/*/src/components/**/*.tsx',
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Patterns to exclude from component discovery
|
|
128
|
+
*/
|
|
129
|
+
const DEFAULT_EXCLUDE_PATTERNS = [
|
|
130
|
+
'**/*.test.*',
|
|
131
|
+
'**/*.spec.*',
|
|
132
|
+
'**/*.stories.*',
|
|
133
|
+
'**/*.story.*',
|
|
134
|
+
'**/__tests__/**',
|
|
135
|
+
'**/__mocks__/**',
|
|
136
|
+
'**/node_modules/**',
|
|
137
|
+
'**/dist/**',
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Discover components from source files
|
|
142
|
+
*
|
|
143
|
+
* This function finds React components by:
|
|
144
|
+
* 1. Looking for TypeScript/TSX files in common component directories
|
|
145
|
+
* 2. Filtering out test files, stories, and internal files
|
|
146
|
+
* 3. Extracting component names from file names or directories
|
|
147
|
+
*/
|
|
148
|
+
export async function discoverComponentsFromSource(
|
|
149
|
+
configDir: string,
|
|
150
|
+
patterns?: string[],
|
|
151
|
+
exclude?: string[]
|
|
152
|
+
): Promise<DiscoveredComponent[]> {
|
|
153
|
+
const searchPatterns = patterns && patterns.length > 0
|
|
154
|
+
? patterns
|
|
155
|
+
: DEFAULT_COMPONENT_PATTERNS;
|
|
156
|
+
|
|
157
|
+
const excludePatterns = [
|
|
158
|
+
...DEFAULT_EXCLUDE_PATTERNS,
|
|
159
|
+
...(exclude ?? []),
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const files = await fg(searchPatterns, {
|
|
163
|
+
cwd: configDir,
|
|
164
|
+
ignore: excludePatterns,
|
|
165
|
+
absolute: false,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Filter to only component-like files (start with uppercase)
|
|
169
|
+
const componentFiles = files.filter((file) => {
|
|
170
|
+
const name = extractComponentName(file);
|
|
171
|
+
return /^[A-Z]/.test(name);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Find associated story files
|
|
175
|
+
const storyPatterns = [
|
|
176
|
+
'**/*.stories.tsx',
|
|
177
|
+
'**/*.stories.ts',
|
|
178
|
+
'**/*.story.tsx',
|
|
179
|
+
'**/*.story.ts',
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const storyFiles = await fg(storyPatterns, {
|
|
183
|
+
cwd: configDir,
|
|
184
|
+
ignore: ['**/node_modules/**', '**/dist/**'],
|
|
185
|
+
absolute: false,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const storyMap = new Map<string, string>();
|
|
189
|
+
for (const storyFile of storyFiles) {
|
|
190
|
+
const name = extractComponentName(storyFile.replace(/\.stories?\.(tsx?|jsx?)$/, '.tsx'));
|
|
191
|
+
storyMap.set(name, storyFile);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Build discovered components
|
|
195
|
+
const components: DiscoveredComponent[] = [];
|
|
196
|
+
|
|
197
|
+
for (const file of componentFiles) {
|
|
198
|
+
const name = extractComponentName(file);
|
|
199
|
+
const absolutePath = resolve(configDir, file);
|
|
200
|
+
|
|
201
|
+
// Look for story file
|
|
202
|
+
const storyFile = storyMap.get(name);
|
|
203
|
+
|
|
204
|
+
components.push({
|
|
205
|
+
name,
|
|
206
|
+
sourcePath: absolutePath,
|
|
207
|
+
relativePath: file,
|
|
208
|
+
storyPath: storyFile ? resolve(configDir, storyFile) : undefined,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Sort by name
|
|
213
|
+
components.sort((a, b) => a.name.localeCompare(b.name));
|
|
214
|
+
|
|
215
|
+
return components;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Discover components from a barrel export file (index.ts)
|
|
220
|
+
*
|
|
221
|
+
* Parses the barrel file to find exported components.
|
|
222
|
+
* This is useful for libraries that expose components through a single entry point.
|
|
223
|
+
*/
|
|
224
|
+
export async function discoverComponentsFromBarrel(
|
|
225
|
+
barrelPath: string,
|
|
226
|
+
configDir: string
|
|
227
|
+
): Promise<DiscoveredComponent[]> {
|
|
228
|
+
const absoluteBarrelPath = resolve(configDir, barrelPath);
|
|
229
|
+
|
|
230
|
+
if (!existsSync(absoluteBarrelPath)) {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const content = await readFile(absoluteBarrelPath, 'utf-8');
|
|
235
|
+
const components: DiscoveredComponent[] = [];
|
|
236
|
+
|
|
237
|
+
// Match export statements like:
|
|
238
|
+
// export { Button } from './Button'
|
|
239
|
+
// export { Card, CardHeader } from './Card'
|
|
240
|
+
// export * from './Modal'
|
|
241
|
+
const exportRegex = /export\s+(?:\*|{([^}]+)})\s+from\s+['"]([^'"]+)['"]/g;
|
|
242
|
+
|
|
243
|
+
let match;
|
|
244
|
+
while ((match = exportRegex.exec(content)) !== null) {
|
|
245
|
+
const exportedNames = match[1];
|
|
246
|
+
const importPath = match[2];
|
|
247
|
+
|
|
248
|
+
// Resolve the import path
|
|
249
|
+
const barrelDir = dirname(absoluteBarrelPath);
|
|
250
|
+
let resolvedPath = resolve(barrelDir, importPath);
|
|
251
|
+
|
|
252
|
+
// Add extension if needed
|
|
253
|
+
if (!resolvedPath.endsWith('.tsx') && !resolvedPath.endsWith('.ts')) {
|
|
254
|
+
if (existsSync(`${resolvedPath}.tsx`)) {
|
|
255
|
+
resolvedPath = `${resolvedPath}.tsx`;
|
|
256
|
+
} else if (existsSync(`${resolvedPath}.ts`)) {
|
|
257
|
+
resolvedPath = `${resolvedPath}.ts`;
|
|
258
|
+
} else if (existsSync(`${resolvedPath}/index.tsx`)) {
|
|
259
|
+
resolvedPath = `${resolvedPath}/index.tsx`;
|
|
260
|
+
} else if (existsSync(`${resolvedPath}/index.ts`)) {
|
|
261
|
+
resolvedPath = `${resolvedPath}/index.ts`;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!existsSync(resolvedPath)) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (exportedNames) {
|
|
270
|
+
// Named exports: { Button, Card }
|
|
271
|
+
const names = exportedNames.split(',').map((n) => n.trim().split(/\s+as\s+/)[0].trim());
|
|
272
|
+
for (const name of names) {
|
|
273
|
+
if (/^[A-Z]/.test(name)) {
|
|
274
|
+
const relativePath = resolvedPath.replace(configDir + '/', '');
|
|
275
|
+
components.push({
|
|
276
|
+
name,
|
|
277
|
+
sourcePath: resolvedPath,
|
|
278
|
+
relativePath,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
// Star export: export * from './Component'
|
|
284
|
+
const name = extractComponentName(importPath);
|
|
285
|
+
if (/^[A-Z]/.test(name)) {
|
|
286
|
+
const relativePath = resolvedPath.replace(configDir + '/', '');
|
|
287
|
+
components.push({
|
|
288
|
+
name,
|
|
289
|
+
sourcePath: resolvedPath,
|
|
290
|
+
relativePath,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return components;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Discover all components using multiple strategies
|
|
301
|
+
*/
|
|
302
|
+
export async function discoverAllComponents(
|
|
303
|
+
configDir: string,
|
|
304
|
+
options: {
|
|
305
|
+
patterns?: string[];
|
|
306
|
+
exclude?: string[];
|
|
307
|
+
barrelFiles?: string[];
|
|
308
|
+
} = {}
|
|
309
|
+
): Promise<DiscoveredComponent[]> {
|
|
310
|
+
const componentsMap = new Map<string, DiscoveredComponent>();
|
|
311
|
+
|
|
312
|
+
// Discover from source files
|
|
313
|
+
const sourceComponents = await discoverComponentsFromSource(
|
|
314
|
+
configDir,
|
|
315
|
+
options.patterns,
|
|
316
|
+
options.exclude
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
for (const comp of sourceComponents) {
|
|
320
|
+
componentsMap.set(comp.name, comp);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Discover from barrel files if specified
|
|
324
|
+
if (options.barrelFiles && options.barrelFiles.length > 0) {
|
|
325
|
+
for (const barrelFile of options.barrelFiles) {
|
|
326
|
+
const barrelComponents = await discoverComponentsFromBarrel(barrelFile, configDir);
|
|
327
|
+
for (const comp of barrelComponents) {
|
|
328
|
+
// Only add if not already found
|
|
329
|
+
if (!componentsMap.has(comp.name)) {
|
|
330
|
+
componentsMap.set(comp.name, comp);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return Array.from(componentsMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
337
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma property mapping DSL
|
|
3
|
+
*
|
|
4
|
+
* Provides helpers for mapping Figma component properties to code props.
|
|
5
|
+
* Inspired by Figma Code Connect's API.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { defineSegment, figma } from '@fragments/core';
|
|
10
|
+
*
|
|
11
|
+
* export default defineSegment({
|
|
12
|
+
* component: Button,
|
|
13
|
+
* meta: {
|
|
14
|
+
* name: 'Button',
|
|
15
|
+
* description: 'Primary action trigger',
|
|
16
|
+
* category: 'actions',
|
|
17
|
+
* figma: 'https://figma.com/file/abc/Design?node-id=1-2',
|
|
18
|
+
* figmaProps: {
|
|
19
|
+
* children: figma.string('Label'),
|
|
20
|
+
* disabled: figma.boolean('Disabled'),
|
|
21
|
+
* variant: figma.enum('Type', {
|
|
22
|
+
* 'Primary': 'primary',
|
|
23
|
+
* 'Secondary': 'secondary',
|
|
24
|
+
* }),
|
|
25
|
+
* },
|
|
26
|
+
* },
|
|
27
|
+
* // ...
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type {
|
|
33
|
+
FigmaStringMapping,
|
|
34
|
+
FigmaBooleanMapping,
|
|
35
|
+
FigmaEnumMapping,
|
|
36
|
+
FigmaInstanceMapping,
|
|
37
|
+
FigmaChildrenMapping,
|
|
38
|
+
FigmaTextContentMapping,
|
|
39
|
+
} from './types.js';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Map a Figma text property to a string prop.
|
|
43
|
+
*
|
|
44
|
+
* @param figmaProperty - The name of the text property in Figma
|
|
45
|
+
* @returns A string mapping descriptor
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* figmaProps: {
|
|
50
|
+
* label: figma.string('Button Text'),
|
|
51
|
+
* placeholder: figma.string('Placeholder'),
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
function string(figmaProperty: string): FigmaStringMapping {
|
|
56
|
+
return {
|
|
57
|
+
__type: 'figma-string',
|
|
58
|
+
figmaProperty,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Map a Figma boolean property to a boolean prop.
|
|
64
|
+
* Optionally map true/false to different values.
|
|
65
|
+
*
|
|
66
|
+
* @param figmaProperty - The name of the boolean property in Figma
|
|
67
|
+
* @param valueMapping - Optional mapping of true/false to other values
|
|
68
|
+
* @returns A boolean mapping descriptor
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```tsx
|
|
72
|
+
* figmaProps: {
|
|
73
|
+
* disabled: figma.boolean('Disabled'),
|
|
74
|
+
* // Map boolean to string values
|
|
75
|
+
* size: figma.boolean('Large', { true: 'lg', false: 'md' }),
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
function boolean(
|
|
80
|
+
figmaProperty: string,
|
|
81
|
+
valueMapping?: { true: unknown; false: unknown }
|
|
82
|
+
): FigmaBooleanMapping {
|
|
83
|
+
return {
|
|
84
|
+
__type: 'figma-boolean',
|
|
85
|
+
figmaProperty,
|
|
86
|
+
valueMapping,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Map a Figma variant property to an enum prop.
|
|
92
|
+
*
|
|
93
|
+
* @param figmaProperty - The name of the variant property in Figma
|
|
94
|
+
* @param valueMapping - Mapping of Figma values to code values
|
|
95
|
+
* @returns An enum mapping descriptor
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```tsx
|
|
99
|
+
* figmaProps: {
|
|
100
|
+
* variant: figma.enum('Type', {
|
|
101
|
+
* 'Primary': 'primary',
|
|
102
|
+
* 'Secondary': 'secondary',
|
|
103
|
+
* 'Outline': 'outline',
|
|
104
|
+
* }),
|
|
105
|
+
* size: figma.enum('Size', {
|
|
106
|
+
* 'Small': 'sm',
|
|
107
|
+
* 'Medium': 'md',
|
|
108
|
+
* 'Large': 'lg',
|
|
109
|
+
* }),
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
function enumValue<T extends Record<string, unknown>>(
|
|
114
|
+
figmaProperty: string,
|
|
115
|
+
valueMapping: T
|
|
116
|
+
): FigmaEnumMapping {
|
|
117
|
+
return {
|
|
118
|
+
__type: 'figma-enum',
|
|
119
|
+
figmaProperty,
|
|
120
|
+
valueMapping,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Reference a nested Figma component instance.
|
|
126
|
+
* Use this when a prop accepts a component that's represented
|
|
127
|
+
* as an instance swap in Figma.
|
|
128
|
+
*
|
|
129
|
+
* @param figmaProperty - The name of the instance property in Figma
|
|
130
|
+
* @returns An instance mapping descriptor
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```tsx
|
|
134
|
+
* figmaProps: {
|
|
135
|
+
* icon: figma.instance('Icon'),
|
|
136
|
+
* avatar: figma.instance('Avatar'),
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
function instance(figmaProperty: string): FigmaInstanceMapping {
|
|
141
|
+
return {
|
|
142
|
+
__type: 'figma-instance',
|
|
143
|
+
figmaProperty,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Render children from specific Figma layers.
|
|
149
|
+
* Use this when children are represented as named layers in Figma.
|
|
150
|
+
*
|
|
151
|
+
* @param layers - Array of layer names to include as children
|
|
152
|
+
* @returns A children mapping descriptor
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* figmaProps: {
|
|
157
|
+
* children: figma.children(['Title', 'Description', 'Actions']),
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
function children(layers: string[]): FigmaChildrenMapping {
|
|
162
|
+
return {
|
|
163
|
+
__type: 'figma-children',
|
|
164
|
+
layers,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract text content from a Figma text layer.
|
|
170
|
+
* Use this when a prop should be the actual text from a layer.
|
|
171
|
+
*
|
|
172
|
+
* @param layer - The name of the text layer in Figma
|
|
173
|
+
* @returns A text content mapping descriptor
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```tsx
|
|
177
|
+
* figmaProps: {
|
|
178
|
+
* title: figma.textContent('Header Text'),
|
|
179
|
+
* description: figma.textContent('Body Text'),
|
|
180
|
+
* }
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
function textContent(layer: string): FigmaTextContentMapping {
|
|
184
|
+
return {
|
|
185
|
+
__type: 'figma-text-content',
|
|
186
|
+
layer,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Figma property mapping helpers.
|
|
192
|
+
*
|
|
193
|
+
* Use these to define how Figma properties map to your component props.
|
|
194
|
+
* The mappings are used for:
|
|
195
|
+
* - Generating accurate code snippets in Figma Dev Mode
|
|
196
|
+
* - AI agents understanding the design-to-code relationship
|
|
197
|
+
* - Automated design verification
|
|
198
|
+
*/
|
|
199
|
+
export const figma = {
|
|
200
|
+
string,
|
|
201
|
+
boolean,
|
|
202
|
+
enum: enumValue,
|
|
203
|
+
instance,
|
|
204
|
+
children,
|
|
205
|
+
textContent,
|
|
206
|
+
} as const;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Helper type to check if a value is a Figma prop mapping
|
|
210
|
+
*/
|
|
211
|
+
export function isFigmaPropMapping(
|
|
212
|
+
value: unknown
|
|
213
|
+
): value is FigmaStringMapping | FigmaBooleanMapping | FigmaEnumMapping | FigmaInstanceMapping | FigmaChildrenMapping | FigmaTextContentMapping {
|
|
214
|
+
if (typeof value !== 'object' || value === null || !('__type' in value)) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
const typeValue = (value as Record<string, unknown>).__type;
|
|
218
|
+
return typeof typeValue === 'string' && typeValue.startsWith('figma-');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Resolve a Figma prop mapping to an actual value given Figma property values.
|
|
223
|
+
*
|
|
224
|
+
* @param mapping - The Figma prop mapping
|
|
225
|
+
* @param figmaValues - Object containing Figma property values
|
|
226
|
+
* @returns The resolved value for the code prop
|
|
227
|
+
*/
|
|
228
|
+
export function resolveFigmaMapping(
|
|
229
|
+
mapping: FigmaStringMapping | FigmaBooleanMapping | FigmaEnumMapping | FigmaInstanceMapping | FigmaChildrenMapping | FigmaTextContentMapping,
|
|
230
|
+
figmaValues: Record<string, unknown>
|
|
231
|
+
): unknown {
|
|
232
|
+
switch (mapping.__type) {
|
|
233
|
+
case 'figma-string':
|
|
234
|
+
return figmaValues[mapping.figmaProperty] ?? '';
|
|
235
|
+
|
|
236
|
+
case 'figma-boolean': {
|
|
237
|
+
const boolValue = figmaValues[mapping.figmaProperty] as boolean;
|
|
238
|
+
if (mapping.valueMapping) {
|
|
239
|
+
return boolValue ? mapping.valueMapping.true : mapping.valueMapping.false;
|
|
240
|
+
}
|
|
241
|
+
return boolValue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
case 'figma-enum': {
|
|
245
|
+
const enumKey = figmaValues[mapping.figmaProperty] as string;
|
|
246
|
+
return mapping.valueMapping[enumKey] ?? enumKey;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
case 'figma-instance':
|
|
250
|
+
// Instance mappings return the instance reference
|
|
251
|
+
return figmaValues[mapping.figmaProperty];
|
|
252
|
+
|
|
253
|
+
case 'figma-children':
|
|
254
|
+
// Children mappings return array of layer contents
|
|
255
|
+
return mapping.layers.map((layer) => figmaValues[layer]);
|
|
256
|
+
|
|
257
|
+
case 'figma-text-content':
|
|
258
|
+
return figmaValues[mapping.layer] ?? '';
|
|
259
|
+
|
|
260
|
+
default:
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
}
|