@fragments-sdk/cli 0.7.3 → 0.7.5
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 +1 -4
- package/README.md +2 -0
- package/dist/bin.js +39 -16
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-D34Q6A7S.js → chunk-AWYCDRPG.js} +8 -2
- package/dist/chunk-AWYCDRPG.js.map +1 -0
- package/dist/{chunk-R2YH7NLN.js → chunk-CR3XHBGM.js} +3 -3
- package/dist/{chunk-QPY4DUFB.js → chunk-EFQ7SIBX.js} +583 -108
- package/dist/chunk-EFQ7SIBX.js.map +1 -0
- package/dist/{chunk-UXLGIGSX.js → chunk-GIC3I2KZ.js} +2 -2
- package/dist/{chunk-R6IZZSE7.js → chunk-JZNATKQA.js} +9 -3
- package/dist/chunk-JZNATKQA.js.map +1 -0
- package/dist/{chunk-P33AKQJW.js → chunk-SFWZ4K7C.js} +8 -2
- package/dist/{chunk-P33AKQJW.js.map → chunk-SFWZ4K7C.js.map} +1 -1
- package/dist/{core-3NMNCLFW.js → core-T7BDYEGO.js} +3 -3
- package/dist/{discovery-AKGA6CJD.js → discovery-Z4RDDFVR.js} +2 -2
- package/dist/{generate-JAUEHKK7.js → generate-C2DKFCFJ.js} +5 -5
- package/dist/index.d.ts +28 -2
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/{init-DZQOT54X.js → init-O3FCHEPN.js} +26 -8
- package/dist/init-O3FCHEPN.js.map +1 -0
- package/dist/mcp-bin.js +3 -3
- package/dist/{scan-OJRCVKK2.js → scan-IYTZDUKG.js} +6 -6
- package/dist/{service-CFFBHW4X.js → service-VA6XKADO.js} +3 -3
- package/dist/{static-viewer-VA2JXSCX.js → static-viewer-5N42MBDR.js} +3 -3
- package/dist/{test-O7DZNKDC.js → test-OMMDWL2W.js} +4 -4
- package/dist/{tokens-N7THFD6J.js → tokens-6VJAHFIG.js} +5 -5
- package/dist/{viewer-QTR7QJMM.js → viewer-IVP5XC7U.js} +37 -17
- package/dist/viewer-IVP5XC7U.js.map +1 -0
- package/package.json +8 -2
- package/src/bin.ts +4 -0
- package/src/commands/add.ts +6 -0
- package/src/commands/init.ts +24 -4
- package/src/commands/validate.ts +24 -2
- package/src/core/config.ts +6 -0
- package/src/core/discovery.ts +7 -1
- package/src/core/index.ts +1 -0
- package/src/core/schema.ts +6 -0
- package/src/core/types.ts +21 -0
- package/src/index.ts +2 -1
- package/src/migrate/detect.ts +4 -0
- package/src/service/snippet-validation.test.ts +209 -0
- package/src/service/snippet-validation.ts +635 -0
- package/src/validators.ts +53 -5
- package/src/viewer/__tests__/viewer-integration.test.ts +8 -0
- package/src/viewer/components/App.tsx +63 -2
- package/src/viewer/components/CodePanel.naming.test.tsx +60 -0
- package/src/viewer/components/CodePanel.tsx +76 -468
- package/src/viewer/components/Layout.tsx +2 -2
- package/src/viewer/components/LeftSidebar.tsx +35 -77
- package/src/viewer/preview-frame.html +1 -1
- package/src/viewer/styles/globals.css +2 -1
- package/src/viewer/utils/a11y-fixes.ts +24 -9
- package/src/viewer/vite-plugin.ts +27 -2
- package/dist/chunk-D34Q6A7S.js.map +0 -1
- package/dist/chunk-QPY4DUFB.js.map +0 -1
- package/dist/chunk-R6IZZSE7.js.map +0 -1
- package/dist/init-DZQOT54X.js.map +0 -1
- package/dist/viewer-QTR7QJMM.js.map +0 -1
- /package/dist/{chunk-R2YH7NLN.js.map → chunk-CR3XHBGM.js.map} +0 -0
- /package/dist/{chunk-UXLGIGSX.js.map → chunk-GIC3I2KZ.js.map} +0 -0
- /package/dist/{core-3NMNCLFW.js.map → core-T7BDYEGO.js.map} +0 -0
- /package/dist/{discovery-AKGA6CJD.js.map → discovery-Z4RDDFVR.js.map} +0 -0
- /package/dist/{generate-JAUEHKK7.js.map → generate-C2DKFCFJ.js.map} +0 -0
- /package/dist/{scan-OJRCVKK2.js.map → scan-IYTZDUKG.js.map} +0 -0
- /package/dist/{service-CFFBHW4X.js.map → service-VA6XKADO.js.map} +0 -0
- /package/dist/{static-viewer-VA2JXSCX.js.map → static-viewer-5N42MBDR.js.map} +0 -0
- /package/dist/{test-O7DZNKDC.js.map → test-OMMDWL2W.js.map} +0 -0
- /package/dist/{tokens-N7THFD6J.js.map → tokens-6VJAHFIG.js.map} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
|
|
2
2
|
import type { FragmentDefinition } from '../../core/index.js';
|
|
3
3
|
import { BRAND } from '../../core/index.js';
|
|
4
|
-
|
|
5
|
-
import { Sidebar, useSidebar,
|
|
4
|
+
|
|
5
|
+
import { Sidebar, useSidebar, Text, FragmentsLogo } from '@fragments/ui';
|
|
6
6
|
|
|
7
7
|
// Fuzzy matching utility
|
|
8
8
|
interface FuzzyMatch {
|
|
@@ -129,7 +129,6 @@ interface LeftSidebarProps {
|
|
|
129
129
|
|
|
130
130
|
export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect, showHealth, onHealthClick }: LeftSidebarProps) {
|
|
131
131
|
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
132
|
-
const { setTheme, resolvedTheme } = useTheme();
|
|
133
132
|
const { isMobile, setOpen } = useSidebar();
|
|
134
133
|
const navRef = useRef<HTMLDivElement>(null);
|
|
135
134
|
|
|
@@ -158,37 +157,19 @@ export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect,
|
|
|
158
157
|
return map;
|
|
159
158
|
}, [searchResults]);
|
|
160
159
|
|
|
161
|
-
|
|
162
|
-
const source = searchResults
|
|
163
|
-
? searchResults.map(r => r.item)
|
|
164
|
-
: fragments;
|
|
165
|
-
|
|
166
|
-
const groups: Record<string, typeof fragments> = {};
|
|
167
|
-
for (const item of source) {
|
|
168
|
-
// Skip invalid fragments
|
|
169
|
-
if (!item.fragment?.meta) continue;
|
|
170
|
-
const category = item.fragment.meta.category || 'uncategorized';
|
|
171
|
-
if (!groups[category]) groups[category] = [];
|
|
172
|
-
groups[category].push(item);
|
|
173
|
-
}
|
|
174
|
-
return groups;
|
|
175
|
-
}, [fragments, searchResults]);
|
|
176
|
-
|
|
160
|
+
// Flat alphabetical list (search results sorted by relevance, otherwise alphabetical)
|
|
177
161
|
const flatItems = useMemo(() => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
for (const [, categoryItems] of sortedEntries) {
|
|
183
|
-
const sorted = [...categoryItems]
|
|
184
|
-
.filter(item => item.fragment?.meta?.name)
|
|
185
|
-
.sort((a, b) =>
|
|
186
|
-
a.fragment.meta.name.toLowerCase().localeCompare(b.fragment.meta.name.toLowerCase())
|
|
187
|
-
);
|
|
188
|
-
items.push(...sorted);
|
|
162
|
+
if (searchResults) {
|
|
163
|
+
return searchResults
|
|
164
|
+
.map(r => r.item)
|
|
165
|
+
.filter(item => item.fragment?.meta?.name);
|
|
189
166
|
}
|
|
190
|
-
return
|
|
191
|
-
|
|
167
|
+
return [...fragments]
|
|
168
|
+
.filter(item => item.fragment?.meta?.name)
|
|
169
|
+
.sort((a, b) =>
|
|
170
|
+
a.fragment.meta.name.toLowerCase().localeCompare(b.fragment.meta.name.toLowerCase())
|
|
171
|
+
);
|
|
172
|
+
}, [fragments, searchResults]);
|
|
192
173
|
|
|
193
174
|
const keyboardItems = useMemo(() => {
|
|
194
175
|
const componentItems = flatItems.map((item) => ({ type: 'component' as const, path: item.path }));
|
|
@@ -257,25 +238,16 @@ export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect,
|
|
|
257
238
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
258
239
|
}, [handleKeyDown]);
|
|
259
240
|
|
|
260
|
-
const sortedEntries = Object.entries(grouped).sort(([a], [b]) =>
|
|
261
|
-
a.toLowerCase().localeCompare(b.toLowerCase())
|
|
262
|
-
);
|
|
263
|
-
|
|
264
241
|
return (
|
|
265
242
|
<>
|
|
266
243
|
{/* Header */}
|
|
267
244
|
<Sidebar.Header>
|
|
268
245
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
<
|
|
272
|
-
size="sm"
|
|
273
|
-
value={resolvedTheme}
|
|
274
|
-
onValueChange={(value) => setTheme(value)}
|
|
275
|
-
aria-label={`Theme: ${resolvedTheme}`}
|
|
276
|
-
/>
|
|
277
|
-
<Sidebar.CollapseToggle />
|
|
246
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
247
|
+
<FragmentsLogo size={20} />
|
|
248
|
+
<Text size="sm" weight="semibold">{BRAND.name}</Text>
|
|
278
249
|
</div>
|
|
250
|
+
<Sidebar.CollapseToggle />
|
|
279
251
|
</div>
|
|
280
252
|
</Sidebar.Header>
|
|
281
253
|
|
|
@@ -283,7 +255,7 @@ export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect,
|
|
|
283
255
|
<div ref={navRef} style={{ flex: 1, minHeight: 0, display: 'flex', overflow: 'hidden' }}>
|
|
284
256
|
<Sidebar.Nav aria-label="Components">
|
|
285
257
|
{onHealthClick && (
|
|
286
|
-
<Sidebar.Section>
|
|
258
|
+
<Sidebar.Section label="Overview">
|
|
287
259
|
<Sidebar.Item
|
|
288
260
|
active={!!showHealth}
|
|
289
261
|
onClick={handleHealthClick}
|
|
@@ -293,36 +265,22 @@ export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect,
|
|
|
293
265
|
</Sidebar.Section>
|
|
294
266
|
)}
|
|
295
267
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
onClick={() => handleSelect(item.path)}
|
|
313
|
-
>
|
|
314
|
-
<HighlightedText
|
|
315
|
-
text={item.fragment.meta.name}
|
|
316
|
-
indices={nameIndices}
|
|
317
|
-
/>
|
|
318
|
-
</Sidebar.Item>
|
|
319
|
-
);
|
|
320
|
-
})}
|
|
321
|
-
</Sidebar.Section>
|
|
322
|
-
);
|
|
323
|
-
})}
|
|
324
|
-
|
|
325
|
-
{Object.keys(grouped).length === 0 && (
|
|
268
|
+
<Sidebar.Section label="Components">
|
|
269
|
+
{flatItems.map((item) => (
|
|
270
|
+
<Sidebar.Item
|
|
271
|
+
key={item.path}
|
|
272
|
+
active={activeFragment === item.path}
|
|
273
|
+
onClick={() => handleSelect(item.path)}
|
|
274
|
+
>
|
|
275
|
+
<HighlightedText
|
|
276
|
+
text={item.fragment.meta.name}
|
|
277
|
+
indices={highlightMap.get(item.path) || []}
|
|
278
|
+
/>
|
|
279
|
+
</Sidebar.Item>
|
|
280
|
+
))}
|
|
281
|
+
</Sidebar.Section>
|
|
282
|
+
|
|
283
|
+
{flatItems.length === 0 && (
|
|
326
284
|
<div style={{ padding: '8px 32px', textAlign: 'center' }}>
|
|
327
285
|
<Text size="sm" color="tertiary">No results</Text>
|
|
328
286
|
</div>
|
|
@@ -332,7 +290,7 @@ export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect,
|
|
|
332
290
|
|
|
333
291
|
{/* Footer */}
|
|
334
292
|
<Sidebar.Footer>
|
|
335
|
-
<
|
|
293
|
+
<Text size="xs" color="tertiary">{fragments.length} components</Text>
|
|
336
294
|
</Sidebar.Footer>
|
|
337
295
|
</>
|
|
338
296
|
);
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
html, body {
|
|
14
14
|
margin: 0;
|
|
15
15
|
padding: 0;
|
|
16
|
-
font-family: var(--fui-font-sans,
|
|
16
|
+
font-family: var(--fui-font-sans, Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
|
|
17
17
|
-webkit-font-smoothing: antialiased;
|
|
18
18
|
-moz-osx-font-smoothing: grayscale;
|
|
19
19
|
width: 100%;
|
|
@@ -65,7 +65,8 @@ html {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
body {
|
|
68
|
-
|
|
68
|
+
margin: 0;
|
|
69
|
+
font-family: var(--fui-font-sans, Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
|
|
69
70
|
background-color: var(--bg-primary);
|
|
70
71
|
color: var(--text-primary);
|
|
71
72
|
-webkit-font-smoothing: antialiased;
|
|
@@ -21,57 +21,63 @@ const WCAG_CRITERIA: Record<string, { id: string; name: string; level: 'A' | 'AA
|
|
|
21
21
|
id: '1.1.1',
|
|
22
22
|
name: 'Non-text Content',
|
|
23
23
|
level: 'A',
|
|
24
|
-
url: 'https://www.w3.org/WAI/
|
|
24
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/non-text-content',
|
|
25
25
|
},
|
|
26
26
|
'1.3.1': {
|
|
27
27
|
id: '1.3.1',
|
|
28
28
|
name: 'Info and Relationships',
|
|
29
29
|
level: 'A',
|
|
30
|
-
url: 'https://www.w3.org/WAI/
|
|
30
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships',
|
|
31
31
|
},
|
|
32
32
|
'2.1.1': {
|
|
33
33
|
id: '2.1.1',
|
|
34
34
|
name: 'Keyboard',
|
|
35
35
|
level: 'A',
|
|
36
|
-
url: 'https://www.w3.org/WAI/
|
|
36
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/keyboard',
|
|
37
37
|
},
|
|
38
38
|
'2.4.4': {
|
|
39
39
|
id: '2.4.4',
|
|
40
40
|
name: 'Link Purpose (In Context)',
|
|
41
41
|
level: 'A',
|
|
42
|
-
url: 'https://www.w3.org/WAI/
|
|
42
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context',
|
|
43
43
|
},
|
|
44
44
|
'4.1.2': {
|
|
45
45
|
id: '4.1.2',
|
|
46
46
|
name: 'Name, Role, Value',
|
|
47
47
|
level: 'A',
|
|
48
|
-
url: 'https://www.w3.org/WAI/
|
|
48
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/name-role-value',
|
|
49
49
|
},
|
|
50
50
|
// Level AA
|
|
51
51
|
'1.4.3': {
|
|
52
52
|
id: '1.4.3',
|
|
53
53
|
name: 'Contrast (Minimum)',
|
|
54
54
|
level: 'AA',
|
|
55
|
-
url: 'https://www.w3.org/WAI/
|
|
55
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum',
|
|
56
56
|
},
|
|
57
57
|
'1.4.4': {
|
|
58
58
|
id: '1.4.4',
|
|
59
59
|
name: 'Resize Text',
|
|
60
60
|
level: 'AA',
|
|
61
|
-
url: 'https://www.w3.org/WAI/
|
|
61
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/resize-text',
|
|
62
62
|
},
|
|
63
63
|
'2.4.6': {
|
|
64
64
|
id: '2.4.6',
|
|
65
65
|
name: 'Headings and Labels',
|
|
66
66
|
level: 'AA',
|
|
67
|
-
url: 'https://www.w3.org/WAI/
|
|
67
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/headings-and-labels',
|
|
68
|
+
},
|
|
69
|
+
'2.5.8': {
|
|
70
|
+
id: '2.5.8',
|
|
71
|
+
name: 'Target Size (Minimum)',
|
|
72
|
+
level: 'AA',
|
|
73
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum',
|
|
68
74
|
},
|
|
69
75
|
// Level AAA
|
|
70
76
|
'1.4.6': {
|
|
71
77
|
id: '1.4.6',
|
|
72
78
|
name: 'Contrast (Enhanced)',
|
|
73
79
|
level: 'AAA',
|
|
74
|
-
url: 'https://www.w3.org/WAI/
|
|
80
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/contrast-enhanced',
|
|
75
81
|
},
|
|
76
82
|
};
|
|
77
83
|
|
|
@@ -178,6 +184,15 @@ const STATIC_FIXES: Record<string, StaticFixSuggestion> = {
|
|
|
178
184
|
goodExample: '<input id="first-name">\n<input id="last-name">',
|
|
179
185
|
wcagCriterion: WCAG_CRITERIA['4.1.2'],
|
|
180
186
|
},
|
|
187
|
+
'target-size-minimum': {
|
|
188
|
+
whyItMatters:
|
|
189
|
+
'Small touch targets are difficult to activate for users with motor impairments or when using touch input.',
|
|
190
|
+
howToFix:
|
|
191
|
+
'Increase the interactive target to at least 24x24 CSS pixels, or add sufficient padding around the control.',
|
|
192
|
+
badExample: '<button style="width: 16px; height: 16px">×</button>',
|
|
193
|
+
goodExample: '<button style="min-width: 24px; min-height: 24px; padding: 4px">×</button>',
|
|
194
|
+
wcagCriterion: WCAG_CRITERIA['2.5.8'],
|
|
195
|
+
},
|
|
181
196
|
};
|
|
182
197
|
|
|
183
198
|
/**
|
|
@@ -182,6 +182,21 @@ export function fragmentsPlugin(options: FragmentsPluginOptions): Plugin[] {
|
|
|
182
182
|
// Add process.env shim and esbuild config for Storybook compatibility
|
|
183
183
|
config() {
|
|
184
184
|
return {
|
|
185
|
+
build: {
|
|
186
|
+
rollupOptions: {
|
|
187
|
+
onwarn(warning: any, defaultHandler: any) {
|
|
188
|
+
// Suppress Node.js module externalization warnings from html2canvas/sanitize-html
|
|
189
|
+
if (
|
|
190
|
+
warning.code === "MODULE_LEVEL_DIRECTIVE" ||
|
|
191
|
+
(warning.message &&
|
|
192
|
+
warning.message.includes("has been externalized"))
|
|
193
|
+
) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
defaultHandler(warning);
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
185
200
|
define: {
|
|
186
201
|
// Shim process.env for story files that use it (e.g., process.env.STORYBOOK_*)
|
|
187
202
|
"process.env": "{}",
|
|
@@ -1666,7 +1681,15 @@ function mergeMetadata(fragment, metadataModule) {
|
|
|
1666
1681
|
if (metadata.variants && fragment.variants) {
|
|
1667
1682
|
for (const metaVariant of metadata.variants) {
|
|
1668
1683
|
const fragmentVariant = fragment.variants.find(v => v.name === metaVariant.name);
|
|
1669
|
-
if (
|
|
1684
|
+
if (!fragmentVariant) continue;
|
|
1685
|
+
|
|
1686
|
+
// Use authored code snippets from fragment metadata when story variants
|
|
1687
|
+
// don't define their own code.
|
|
1688
|
+
if (metaVariant.code && !fragmentVariant.code) {
|
|
1689
|
+
fragmentVariant.code = metaVariant.code;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
if (metaVariant.figma && !fragmentVariant.figma) {
|
|
1670
1693
|
fragmentVariant.figma = metaVariant.figma;
|
|
1671
1694
|
}
|
|
1672
1695
|
}
|
|
@@ -1753,7 +1776,9 @@ export async function loadFragment(path) {
|
|
|
1753
1776
|
}
|
|
1754
1777
|
}
|
|
1755
1778
|
|
|
1756
|
-
|
|
1779
|
+
if (fragment) {
|
|
1780
|
+
loadedFragments.set(path, fragment);
|
|
1781
|
+
}
|
|
1757
1782
|
return fragment;
|
|
1758
1783
|
}
|
|
1759
1784
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/discovery.ts"],"sourcesContent":["import { resolve, dirname, basename } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport fg from 'fast-glob';\nimport type { FragmentsConfig } from './types.js';\nimport { BRAND } from './constants.js';\n\nexport interface DiscoveredFile {\n /** Absolute path to the file */\n absolutePath: string;\n /** Path relative to config directory */\n relativePath: string;\n}\n\n/**\n * Discovered component with source file information\n */\nexport interface DiscoveredComponent {\n /** Component name (e.g., \"Button\") */\n name: string;\n /** Absolute path to the component source file */\n sourcePath: string;\n /** Path relative to config directory */\n relativePath: string;\n /** Path to storybook file if found */\n storyPath?: string;\n}\n\n/**\n * Discover block files (*.block.ts) under the config directory.\n * Also discovers legacy *.recipe.ts files for backward compatibility.\n */\nexport async function discoverBlockFiles(\n configDir: string,\n exclude?: string[]\n): Promise<DiscoveredFile[]> {\n const patterns = [\n `**/*${BRAND.blockFileExtension}`,\n `**/*${BRAND.recipeFileExtension}`,\n ];\n const files = await fg(patterns, {\n cwd: configDir,\n ignore: exclude ?? ['**/node_modules/**', '**/dist/**'],\n absolute: false,\n });\n\n return files.map((relativePath) => ({\n relativePath,\n absolutePath: resolve(configDir, relativePath),\n }));\n}\n\n/**\n * @deprecated Use discoverBlockFiles instead\n */\nexport const discoverRecipeFiles = discoverBlockFiles;\n\n/**\n * Discover fragment files matching the config patterns\n */\nexport async function discoverFragmentFiles(\n config: FragmentsConfig,\n configDir: string\n): Promise<DiscoveredFile[]> {\n const files = await fg(config.include, {\n cwd: configDir,\n ignore: config.exclude ?? [],\n absolute: false,\n });\n\n return files.map((relativePath) => ({\n relativePath,\n absolutePath: resolve(configDir, relativePath),\n }));\n}\n\n/**\n * Discover component files for coverage validation\n */\nexport async function discoverComponentFiles(\n config: FragmentsConfig,\n configDir: string\n): Promise<DiscoveredFile[]> {\n if (!config.components || config.components.length === 0) {\n return [];\n }\n\n const files = await fg(config.components, {\n cwd: configDir,\n ignore: [\n ...(config.exclude ?? []),\n // Exclude fragment files themselves\n ...config.include,\n // Exclude test files\n '**/*.test.*',\n '**/*.spec.*',\n '**/__tests__/**',\n ],\n absolute: false,\n });\n\n return files.map((relativePath) => ({\n relativePath,\n absolutePath: resolve(configDir, relativePath),\n }));\n}\n\n/**\n * Extract component name from file path\n */\nexport function extractComponentName(filePath: string): string {\n // Handle index.tsx files - use parent directory name\n const parts = filePath.replace(/\\\\/g, '/').split('/');\n const fileName = parts[parts.length - 1];\n\n if (fileName === 'index.tsx' || fileName === 'index.ts') {\n return parts[parts.length - 2] ?? 'Unknown';\n }\n\n // Remove extension\n return fileName.replace(/\\.(tsx?|jsx?)$/, '');\n}\n\n/**\n * Default patterns for component discovery\n */\nconst DEFAULT_COMPONENT_PATTERNS = [\n 'src/components/**/*.tsx',\n 'src/components/**/index.tsx',\n 'components/**/*.tsx',\n 'lib/components/**/*.tsx',\n 'packages/*/src/components/**/*.tsx',\n];\n\n/**\n * Patterns to exclude from component discovery\n */\nconst DEFAULT_EXCLUDE_PATTERNS = [\n '**/*.test.*',\n '**/*.spec.*',\n '**/*.stories.*',\n '**/*.story.*',\n '**/__tests__/**',\n '**/__mocks__/**',\n '**/node_modules/**',\n '**/dist/**',\n];\n\n/**\n * Discover components from source files\n *\n * This function finds React components by:\n * 1. Looking for TypeScript/TSX files in common component directories\n * 2. Filtering out test files, stories, and internal files\n * 3. Extracting component names from file names or directories\n */\nexport async function discoverComponentsFromSource(\n configDir: string,\n patterns?: string[],\n exclude?: string[]\n): Promise<DiscoveredComponent[]> {\n const searchPatterns = patterns && patterns.length > 0\n ? patterns\n : DEFAULT_COMPONENT_PATTERNS;\n\n const excludePatterns = [\n ...DEFAULT_EXCLUDE_PATTERNS,\n ...(exclude ?? []),\n ];\n\n const files = await fg(searchPatterns, {\n cwd: configDir,\n ignore: excludePatterns,\n absolute: false,\n });\n\n // Filter to only component-like files (start with uppercase)\n const componentFiles = files.filter((file) => {\n const name = extractComponentName(file);\n return /^[A-Z]/.test(name);\n });\n\n // Find associated story files\n const storyPatterns = [\n '**/*.stories.tsx',\n '**/*.stories.ts',\n '**/*.story.tsx',\n '**/*.story.ts',\n ];\n\n const storyFiles = await fg(storyPatterns, {\n cwd: configDir,\n ignore: ['**/node_modules/**', '**/dist/**'],\n absolute: false,\n });\n\n const storyMap = new Map<string, string>();\n for (const storyFile of storyFiles) {\n const name = extractComponentName(storyFile.replace(/\\.stories?\\.(tsx?|jsx?)$/, '.tsx'));\n storyMap.set(name, storyFile);\n }\n\n // Build discovered components\n const components: DiscoveredComponent[] = [];\n\n for (const file of componentFiles) {\n const name = extractComponentName(file);\n const absolutePath = resolve(configDir, file);\n\n // Look for story file\n const storyFile = storyMap.get(name);\n\n components.push({\n name,\n sourcePath: absolutePath,\n relativePath: file,\n storyPath: storyFile ? resolve(configDir, storyFile) : undefined,\n });\n }\n\n // Sort by name\n components.sort((a, b) => a.name.localeCompare(b.name));\n\n return components;\n}\n\n/**\n * Discover components from a barrel export file (index.ts)\n *\n * Parses the barrel file to find exported components.\n * This is useful for libraries that expose components through a single entry point.\n */\nexport async function discoverComponentsFromBarrel(\n barrelPath: string,\n configDir: string\n): Promise<DiscoveredComponent[]> {\n const absoluteBarrelPath = resolve(configDir, barrelPath);\n\n if (!existsSync(absoluteBarrelPath)) {\n return [];\n }\n\n const content = await readFile(absoluteBarrelPath, 'utf-8');\n const components: DiscoveredComponent[] = [];\n\n // Match export statements like:\n // export { Button } from './Button'\n // export { Card, CardHeader } from './Card'\n // export * from './Modal'\n const exportRegex = /export\\s+(?:\\*|{([^}]+)})\\s+from\\s+['\"]([^'\"]+)['\"]/g;\n\n let match;\n while ((match = exportRegex.exec(content)) !== null) {\n const exportedNames = match[1];\n const importPath = match[2];\n\n // Resolve the import path\n const barrelDir = dirname(absoluteBarrelPath);\n let resolvedPath = resolve(barrelDir, importPath);\n\n // Add extension if needed\n if (!resolvedPath.endsWith('.tsx') && !resolvedPath.endsWith('.ts')) {\n if (existsSync(`${resolvedPath}.tsx`)) {\n resolvedPath = `${resolvedPath}.tsx`;\n } else if (existsSync(`${resolvedPath}.ts`)) {\n resolvedPath = `${resolvedPath}.ts`;\n } else if (existsSync(`${resolvedPath}/index.tsx`)) {\n resolvedPath = `${resolvedPath}/index.tsx`;\n } else if (existsSync(`${resolvedPath}/index.ts`)) {\n resolvedPath = `${resolvedPath}/index.ts`;\n }\n }\n\n if (!existsSync(resolvedPath)) {\n continue;\n }\n\n if (exportedNames) {\n // Named exports: { Button, Card }\n const names = exportedNames.split(',').map((n) => n.trim().split(/\\s+as\\s+/)[0].trim());\n for (const name of names) {\n if (/^[A-Z]/.test(name)) {\n const relativePath = resolvedPath.replace(configDir + '/', '');\n components.push({\n name,\n sourcePath: resolvedPath,\n relativePath,\n });\n }\n }\n } else {\n // Star export: export * from './Component'\n const name = extractComponentName(importPath);\n if (/^[A-Z]/.test(name)) {\n const relativePath = resolvedPath.replace(configDir + '/', '');\n components.push({\n name,\n sourcePath: resolvedPath,\n relativePath,\n });\n }\n }\n }\n\n return components;\n}\n\n/**\n * Default glob patterns for discovering token files (SCSS/CSS with custom properties)\n */\nconst DEFAULT_TOKEN_PATTERNS = [\n 'src/**/tokens/**/_variables.scss',\n 'src/**/tokens/**/variables.scss',\n 'src/**/styles/**/variables.scss',\n 'src/**/styles/**/tokens.scss',\n 'src/**/styles/**/variables.css',\n 'src/**/theme/**/_variables.scss',\n 'src/**/theme/**/tokens.css',\n];\n\n/**\n * Discover token files (SCSS/CSS files containing CSS custom properties).\n * Uses config.tokens.include patterns if provided, otherwise falls back\n * to default patterns that match common project structures.\n */\nexport async function discoverTokenFiles(\n configDir: string,\n patterns?: string[],\n exclude?: string[]\n): Promise<DiscoveredFile[]> {\n const searchPatterns = patterns && patterns.length > 0\n ? patterns\n : DEFAULT_TOKEN_PATTERNS;\n\n const files = await fg(searchPatterns, {\n cwd: configDir,\n ignore: exclude ?? ['**/node_modules/**', '**/dist/**'],\n absolute: false,\n });\n\n return files.map((relativePath) => ({\n relativePath,\n absolutePath: resolve(configDir, relativePath),\n }));\n}\n\n/**\n * Discover fragment files from installed packages that declare a \"fragments\" field\n * in their package.json. This allows consumer projects to see components from\n * installed packages (e.g. @fragments-sdk/ui) in the dev viewer.\n */\nexport async function discoverInstalledFragments(\n projectRoot: string\n): Promise<DiscoveredFile[]> {\n const pkgJsonPath = resolve(projectRoot, 'package.json');\n if (!existsSync(pkgJsonPath)) return [];\n\n const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));\n const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };\n const results: DiscoveredFile[] = [];\n\n for (const depName of Object.keys(allDeps)) {\n const depDir = resolve(projectRoot, 'node_modules', depName);\n const depPkgPath = resolve(depDir, 'package.json');\n if (!existsSync(depPkgPath)) continue;\n\n const depPkg = JSON.parse(await readFile(depPkgPath, 'utf-8'));\n if (!depPkg.fragments) continue;\n\n // Package declares fragments — scan for source fragment files\n const files = await fg(\n [`src/**/*${BRAND.fileExtension}`, 'src/**/*.stories.tsx'],\n { cwd: depDir, ignore: ['**/node_modules/**'], absolute: false }\n );\n\n for (const rel of files) {\n results.push({\n relativePath: `${depName}/${rel}`,\n absolutePath: resolve(depDir, rel),\n });\n }\n }\n\n return results;\n}\n\n/**\n * Discover all components using multiple strategies\n */\nexport async function discoverAllComponents(\n configDir: string,\n options: {\n patterns?: string[];\n exclude?: string[];\n barrelFiles?: string[];\n } = {}\n): Promise<DiscoveredComponent[]> {\n const componentsMap = new Map<string, DiscoveredComponent>();\n\n // Discover from source files\n const sourceComponents = await discoverComponentsFromSource(\n configDir,\n options.patterns,\n options.exclude\n );\n\n for (const comp of sourceComponents) {\n componentsMap.set(comp.name, comp);\n }\n\n // Discover from barrel files if specified\n if (options.barrelFiles && options.barrelFiles.length > 0) {\n for (const barrelFile of options.barrelFiles) {\n const barrelComponents = await discoverComponentsFromBarrel(barrelFile, configDir);\n for (const comp of barrelComponents) {\n // Only add if not already found\n if (!componentsMap.has(comp.name)) {\n componentsMap.set(comp.name, comp);\n }\n }\n }\n }\n\n return Array.from(componentsMap.values()).sort((a, b) => a.name.localeCompare(b.name));\n}\n"],"mappings":";;;;;;AAAA,SAAS,SAAS,eAAyB;AAC3C,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AA6Bf,eAAsB,mBACpB,WACA,SAC2B;AAC3B,QAAM,WAAW;AAAA,IACf,OAAO,MAAM,kBAAkB;AAAA,IAC/B,OAAO,MAAM,mBAAmB;AAAA,EAClC;AACA,QAAM,QAAQ,MAAM,GAAG,UAAU;AAAA,IAC/B,KAAK;AAAA,IACL,QAAQ,WAAW,CAAC,sBAAsB,YAAY;AAAA,IACtD,UAAU;AAAA,EACZ,CAAC;AAED,SAAO,MAAM,IAAI,CAAC,kBAAkB;AAAA,IAClC;AAAA,IACA,cAAc,QAAQ,WAAW,YAAY;AAAA,EAC/C,EAAE;AACJ;AAKO,IAAM,sBAAsB;AAKnC,eAAsB,sBACpB,QACA,WAC2B;AAC3B,QAAM,QAAQ,MAAM,GAAG,OAAO,SAAS;AAAA,IACrC,KAAK;AAAA,IACL,QAAQ,OAAO,WAAW,CAAC;AAAA,IAC3B,UAAU;AAAA,EACZ,CAAC;AAED,SAAO,MAAM,IAAI,CAAC,kBAAkB;AAAA,IAClC;AAAA,IACA,cAAc,QAAQ,WAAW,YAAY;AAAA,EAC/C,EAAE;AACJ;AAKA,eAAsB,uBACpB,QACA,WAC2B;AAC3B,MAAI,CAAC,OAAO,cAAc,OAAO,WAAW,WAAW,GAAG;AACxD,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,QAAQ,MAAM,GAAG,OAAO,YAAY;AAAA,IACxC,KAAK;AAAA,IACL,QAAQ;AAAA,MACN,GAAI,OAAO,WAAW,CAAC;AAAA;AAAA,MAEvB,GAAG,OAAO;AAAA;AAAA,MAEV;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU;AAAA,EACZ,CAAC;AAED,SAAO,MAAM,IAAI,CAAC,kBAAkB;AAAA,IAClC;AAAA,IACA,cAAc,QAAQ,WAAW,YAAY;AAAA,EAC/C,EAAE;AACJ;AAKO,SAAS,qBAAqB,UAA0B;AAE7D,QAAM,QAAQ,SAAS,QAAQ,OAAO,GAAG,EAAE,MAAM,GAAG;AACpD,QAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AAEvC,MAAI,aAAa,eAAe,aAAa,YAAY;AACvD,WAAO,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,EACpC;AAGA,SAAO,SAAS,QAAQ,kBAAkB,EAAE;AAC9C;AAKA,IAAM,6BAA6B;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKA,IAAM,2BAA2B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAUA,eAAsB,6BACpB,WACA,UACA,SACgC;AAChC,QAAM,iBAAiB,YAAY,SAAS,SAAS,IACjD,WACA;AAEJ,QAAM,kBAAkB;AAAA,IACtB,GAAG;AAAA,IACH,GAAI,WAAW,CAAC;AAAA,EAClB;AAEA,QAAM,QAAQ,MAAM,GAAG,gBAAgB;AAAA,IACrC,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ,CAAC;AAGD,QAAM,iBAAiB,MAAM,OAAO,CAAC,SAAS;AAC5C,UAAM,OAAO,qBAAqB,IAAI;AACtC,WAAO,SAAS,KAAK,IAAI;AAAA,EAC3B,CAAC;AAGD,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,aAAa,MAAM,GAAG,eAAe;AAAA,IACzC,KAAK;AAAA,IACL,QAAQ,CAAC,sBAAsB,YAAY;AAAA,IAC3C,UAAU;AAAA,EACZ,CAAC;AAED,QAAM,WAAW,oBAAI,IAAoB;AACzC,aAAW,aAAa,YAAY;AAClC,UAAM,OAAO,qBAAqB,UAAU,QAAQ,4BAA4B,MAAM,CAAC;AACvF,aAAS,IAAI,MAAM,SAAS;AAAA,EAC9B;AAGA,QAAM,aAAoC,CAAC;AAE3C,aAAW,QAAQ,gBAAgB;AACjC,UAAM,OAAO,qBAAqB,IAAI;AACtC,UAAM,eAAe,QAAQ,WAAW,IAAI;AAG5C,UAAM,YAAY,SAAS,IAAI,IAAI;AAEnC,eAAW,KAAK;AAAA,MACd;AAAA,MACA,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,WAAW,YAAY,QAAQ,WAAW,SAAS,IAAI;AAAA,IACzD,CAAC;AAAA,EACH;AAGA,aAAW,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAEtD,SAAO;AACT;AAQA,eAAsB,6BACpB,YACA,WACgC;AAChC,QAAM,qBAAqB,QAAQ,WAAW,UAAU;AAExD,MAAI,CAAC,WAAW,kBAAkB,GAAG;AACnC,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,UAAU,MAAM,SAAS,oBAAoB,OAAO;AAC1D,QAAM,aAAoC,CAAC;AAM3C,QAAM,cAAc;AAEpB,MAAI;AACJ,UAAQ,QAAQ,YAAY,KAAK,OAAO,OAAO,MAAM;AACnD,UAAM,gBAAgB,MAAM,CAAC;AAC7B,UAAM,aAAa,MAAM,CAAC;AAG1B,UAAM,YAAY,QAAQ,kBAAkB;AAC5C,QAAI,eAAe,QAAQ,WAAW,UAAU;AAGhD,QAAI,CAAC,aAAa,SAAS,MAAM,KAAK,CAAC,aAAa,SAAS,KAAK,GAAG;AACnE,UAAI,WAAW,GAAG,YAAY,MAAM,GAAG;AACrC,uBAAe,GAAG,YAAY;AAAA,MAChC,WAAW,WAAW,GAAG,YAAY,KAAK,GAAG;AAC3C,uBAAe,GAAG,YAAY;AAAA,MAChC,WAAW,WAAW,GAAG,YAAY,YAAY,GAAG;AAClD,uBAAe,GAAG,YAAY;AAAA,MAChC,WAAW,WAAW,GAAG,YAAY,WAAW,GAAG;AACjD,uBAAe,GAAG,YAAY;AAAA,MAChC;AAAA,IACF;AAEA,QAAI,CAAC,WAAW,YAAY,GAAG;AAC7B;AAAA,IACF;AAEA,QAAI,eAAe;AAEjB,YAAM,QAAQ,cAAc,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,UAAU,EAAE,CAAC,EAAE,KAAK,CAAC;AACtF,iBAAW,QAAQ,OAAO;AACxB,YAAI,SAAS,KAAK,IAAI,GAAG;AACvB,gBAAM,eAAe,aAAa,QAAQ,YAAY,KAAK,EAAE;AAC7D,qBAAW,KAAK;AAAA,YACd;AAAA,YACA,YAAY;AAAA,YACZ;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,OAAO;AAEL,YAAM,OAAO,qBAAqB,UAAU;AAC5C,UAAI,SAAS,KAAK,IAAI,GAAG;AACvB,cAAM,eAAe,aAAa,QAAQ,YAAY,KAAK,EAAE;AAC7D,mBAAW,KAAK;AAAA,UACd;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKA,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,eAAsB,mBACpB,WACA,UACA,SAC2B;AAC3B,QAAM,iBAAiB,YAAY,SAAS,SAAS,IACjD,WACA;AAEJ,QAAM,QAAQ,MAAM,GAAG,gBAAgB;AAAA,IACrC,KAAK;AAAA,IACL,QAAQ,WAAW,CAAC,sBAAsB,YAAY;AAAA,IACtD,UAAU;AAAA,EACZ,CAAC;AAED,SAAO,MAAM,IAAI,CAAC,kBAAkB;AAAA,IAClC;AAAA,IACA,cAAc,QAAQ,WAAW,YAAY;AAAA,EAC/C,EAAE;AACJ;AAOA,eAAsB,2BACpB,aAC2B;AAC3B,QAAM,cAAc,QAAQ,aAAa,cAAc;AACvD,MAAI,CAAC,WAAW,WAAW,EAAG,QAAO,CAAC;AAEtC,QAAM,UAAU,KAAK,MAAM,MAAM,SAAS,aAAa,OAAO,CAAC;AAC/D,QAAM,UAAU,EAAE,GAAG,QAAQ,cAAc,GAAG,QAAQ,gBAAgB;AACtE,QAAM,UAA4B,CAAC;AAEnC,aAAW,WAAW,OAAO,KAAK,OAAO,GAAG;AAC1C,UAAM,SAAS,QAAQ,aAAa,gBAAgB,OAAO;AAC3D,UAAM,aAAa,QAAQ,QAAQ,cAAc;AACjD,QAAI,CAAC,WAAW,UAAU,EAAG;AAE7B,UAAM,SAAS,KAAK,MAAM,MAAM,SAAS,YAAY,OAAO,CAAC;AAC7D,QAAI,CAAC,OAAO,UAAW;AAGvB,UAAM,QAAQ,MAAM;AAAA,MAClB,CAAC,WAAW,MAAM,aAAa,IAAI,sBAAsB;AAAA,MACzD,EAAE,KAAK,QAAQ,QAAQ,CAAC,oBAAoB,GAAG,UAAU,MAAM;AAAA,IACjE;AAEA,eAAW,OAAO,OAAO;AACvB,cAAQ,KAAK;AAAA,QACX,cAAc,GAAG,OAAO,IAAI,GAAG;AAAA,QAC/B,cAAc,QAAQ,QAAQ,GAAG;AAAA,MACnC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAKA,eAAsB,sBACpB,WACA,UAII,CAAC,GAC2B;AAChC,QAAM,gBAAgB,oBAAI,IAAiC;AAG3D,QAAM,mBAAmB,MAAM;AAAA,IAC7B;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAEA,aAAW,QAAQ,kBAAkB;AACnC,kBAAc,IAAI,KAAK,MAAM,IAAI;AAAA,EACnC;AAGA,MAAI,QAAQ,eAAe,QAAQ,YAAY,SAAS,GAAG;AACzD,eAAW,cAAc,QAAQ,aAAa;AAC5C,YAAM,mBAAmB,MAAM,6BAA6B,YAAY,SAAS;AACjF,iBAAW,QAAQ,kBAAkB;AAEnC,YAAI,CAAC,cAAc,IAAI,KAAK,IAAI,GAAG;AACjC,wBAAc,IAAI,KAAK,MAAM,IAAI;AAAA,QACnC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,cAAc,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACvF;","names":[]}
|