@fragments-sdk/cli 0.7.0 → 0.7.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 +77 -14
- package/dist/bin.js +247 -247
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-CVXKXVOY.js → chunk-3T6QL7IY.js} +47 -29
- package/dist/chunk-3T6QL7IY.js.map +1 -0
- package/dist/{chunk-7OPWMLOE.js → chunk-7KUSBMI4.js} +114 -112
- package/dist/chunk-7KUSBMI4.js.map +1 -0
- package/dist/{chunk-XHUDJNN3.js → chunk-DH4ETVSM.js} +18 -18
- package/dist/chunk-DH4ETVSM.js.map +1 -0
- package/dist/{chunk-RVRTRESS.js → chunk-DQHWLAUV.js} +29 -29
- package/dist/chunk-DQHWLAUV.js.map +1 -0
- package/dist/{chunk-6JBGU74P.js → chunk-GHYYFAQN.js} +23 -23
- package/dist/chunk-GHYYFAQN.js.map +1 -0
- package/dist/{chunk-NWQ4CJOQ.js → chunk-GKX2HPZ6.js} +40 -40
- package/dist/chunk-GKX2HPZ6.js.map +1 -0
- package/dist/{chunk-TJ34N7C7.js → chunk-OOGTG5FM.js} +34 -33
- package/dist/chunk-OOGTG5FM.js.map +1 -0
- package/dist/{core-W2HYIQW6.js → core-UQXZTBFZ.js} +24 -26
- package/dist/{generate-LMTISDIJ.js → generate-GP6ZLAQB.js} +5 -5
- package/dist/generate-GP6ZLAQB.js.map +1 -0
- package/dist/index.d.ts +23 -27
- package/dist/index.js +10 -10
- package/dist/{init-7CHRKQ7P.js → init-W72WBSU2.js} +5 -5
- package/dist/{init-7CHRKQ7P.js.map → init-W72WBSU2.js.map} +1 -1
- package/dist/mcp-bin.js +73 -73
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-V54HWRDY.js +12 -0
- package/dist/{service-T2L7VLTE.js → service-PVGTYUKX.js} +6 -6
- package/dist/{static-viewer-GBR7YNF3.js → static-viewer-KILKIVN7.js} +4 -4
- package/dist/{test-OJRXNDO2.js → test-3YRYQRGV.js} +19 -19
- package/dist/test-3YRYQRGV.js.map +1 -0
- package/dist/{tokens-3BWDESVM.js → tokens-IXSQHPQK.js} +5 -5
- package/dist/{viewer-SUFOISZM.js → viewer-K42REJU2.js} +199 -199
- package/dist/viewer-K42REJU2.js.map +1 -0
- package/package.json +13 -2
- package/src/ai.ts +5 -5
- package/src/analyze.ts +11 -11
- package/src/bin.ts +1 -1
- package/src/build.ts +37 -35
- package/src/commands/a11y.ts +6 -6
- package/src/commands/add.ts +11 -11
- package/src/commands/audit.ts +4 -4
- package/src/commands/baseline.ts +3 -3
- package/src/commands/build.ts +8 -8
- package/src/commands/compare.ts +20 -20
- package/src/commands/context.ts +16 -16
- package/src/commands/enhance.ts +36 -36
- package/src/commands/generate.ts +1 -1
- package/src/commands/graph.ts +5 -5
- package/src/commands/init.ts +1 -1
- package/src/commands/link/figma.ts +82 -82
- package/src/commands/link/index.ts +3 -3
- package/src/commands/link/storybook.ts +9 -9
- package/src/commands/list.ts +2 -2
- package/src/commands/reset.ts +15 -15
- package/src/commands/scan.ts +27 -27
- package/src/commands/storygen.ts +24 -24
- package/src/commands/validate.ts +2 -2
- package/src/commands/verify.ts +8 -8
- package/src/core/auto-props.ts +4 -4
- package/src/core/composition.test.ts +36 -36
- package/src/core/composition.ts +19 -19
- package/src/core/config.ts +6 -6
- package/src/core/{defineSegment.ts → defineFragment.ts} +16 -22
- package/src/core/discovery.ts +6 -6
- package/src/core/figma.ts +2 -2
- package/src/core/graph-extractor.test.ts +77 -77
- package/src/core/graph-extractor.ts +32 -32
- package/src/core/importAnalyzer.ts +1 -1
- package/src/core/index.ts +22 -23
- package/src/core/loader.ts +21 -24
- package/src/core/node.ts +5 -5
- package/src/core/parser.ts +71 -31
- package/src/core/previewLoader.ts +1 -1
- package/src/core/schema.ts +16 -16
- package/src/core/storyAdapter.test.ts +87 -87
- package/src/core/storyAdapter.ts +16 -16
- package/src/core/token-parser.ts +9 -1
- package/src/core/types.ts +21 -26
- package/src/diff.ts +22 -22
- package/src/index.ts +2 -2
- package/src/mcp/server.ts +80 -80
- package/src/migrate/__tests__/utils/utils.test.ts +3 -3
- package/src/migrate/bin.ts +4 -4
- package/src/migrate/converter.ts +16 -16
- package/src/migrate/index.ts +3 -3
- package/src/migrate/migrate.ts +3 -3
- package/src/migrate/parser.ts +8 -8
- package/src/migrate/report.ts +2 -2
- package/src/migrate/types.ts +4 -4
- package/src/screenshot.ts +22 -22
- package/src/service/__tests__/props-extractor.test.ts +15 -15
- package/src/service/analytics.ts +39 -39
- package/src/service/enhance/codebase-scanner.ts +1 -1
- package/src/service/enhance/index.ts +1 -1
- package/src/service/enhance/props-extractor.ts +2 -2
- package/src/service/enhance/types.ts +2 -2
- package/src/service/index.ts +2 -2
- package/src/service/metrics-store.ts +1 -1
- package/src/service/patch-generator.ts +1 -1
- package/src/setup.ts +52 -52
- package/src/shared/dev-server-client.ts +7 -7
- package/src/shared/fragment-loader.ts +59 -0
- package/src/shared/index.ts +1 -1
- package/src/shared/types.ts +4 -4
- package/src/static-viewer.ts +35 -35
- package/src/test/discovery.ts +6 -6
- package/src/test/index.ts +5 -5
- package/src/test/reporters/console.ts +1 -1
- package/src/test/reporters/junit.ts +1 -1
- package/src/test/runner.ts +7 -7
- package/src/test/types.ts +3 -3
- package/src/test/watch.ts +9 -9
- package/src/validators.ts +26 -26
- package/src/viewer/__tests__/render-utils.test.ts +28 -28
- package/src/viewer/__tests__/viewer-integration.test.ts +4 -4
- package/src/viewer/cli/health.ts +26 -26
- package/src/viewer/components/App.tsx +79 -79
- package/src/viewer/components/BottomPanel.tsx +17 -17
- package/src/viewer/components/CodePanel.tsx +3 -3
- package/src/viewer/components/CommandPalette.tsx +11 -11
- package/src/viewer/components/ComponentGraph.tsx +28 -28
- package/src/viewer/components/ComponentHeader.tsx +2 -2
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/FigmaEmbed.tsx +9 -9
- package/src/viewer/components/HealthDashboard.tsx +17 -17
- package/src/viewer/components/InteractionsPanel.tsx +2 -2
- package/src/viewer/components/IsolatedPreviewFrame.tsx +6 -6
- package/src/viewer/components/IsolatedRender.tsx +10 -10
- package/src/viewer/components/LeftSidebar.tsx +28 -28
- package/src/viewer/components/MultiViewportPreview.tsx +14 -14
- package/src/viewer/components/PreviewArea.tsx +11 -11
- package/src/viewer/components/PreviewFrameHost.tsx +51 -51
- package/src/viewer/components/RightSidebar.tsx +9 -9
- package/src/viewer/components/Sidebar.tsx +17 -17
- package/src/viewer/components/StoryRenderer.tsx +2 -2
- package/src/viewer/components/TokenStylePanel.tsx +1 -1
- package/src/viewer/components/UsageSection.tsx +2 -2
- package/src/viewer/components/VariantMatrix.tsx +11 -11
- package/src/viewer/components/VariantRenderer.tsx +3 -3
- package/src/viewer/components/VariantTabs.tsx +2 -2
- package/src/viewer/components/_future/CreatePage.tsx +6 -6
- package/src/viewer/composition-renderer.ts +11 -11
- package/src/viewer/entry.tsx +40 -40
- package/src/viewer/hooks/useFigmaIntegration.ts +1 -1
- package/src/viewer/hooks/usePreviewBridge.ts +5 -5
- package/src/viewer/hooks/useUrlState.ts +6 -6
- package/src/viewer/index.ts +2 -2
- package/src/viewer/intelligence/healthReport.ts +17 -17
- package/src/viewer/intelligence/styleDrift.ts +1 -1
- package/src/viewer/intelligence/usageScanner.ts +1 -1
- package/src/viewer/render-template.html +1 -1
- package/src/viewer/render-utils.ts +21 -21
- package/src/viewer/server.ts +18 -18
- package/src/viewer/utils/detectRelationships.ts +22 -22
- package/src/viewer/vite-plugin.ts +213 -213
- package/dist/chunk-6JBGU74P.js.map +0 -1
- package/dist/chunk-7OPWMLOE.js.map +0 -1
- package/dist/chunk-CVXKXVOY.js.map +0 -1
- package/dist/chunk-NWQ4CJOQ.js.map +0 -1
- package/dist/chunk-RVRTRESS.js.map +0 -1
- package/dist/chunk-TJ34N7C7.js.map +0 -1
- package/dist/chunk-XHUDJNN3.js.map +0 -1
- package/dist/generate-LMTISDIJ.js.map +0 -1
- package/dist/scan-WY23TJCP.js +0 -12
- package/dist/test-OJRXNDO2.js.map +0 -1
- package/dist/viewer-SUFOISZM.js.map +0 -1
- package/src/shared/segment-loader.ts +0 -59
- /package/dist/{core-W2HYIQW6.js.map → core-UQXZTBFZ.js.map} +0 -0
- /package/dist/{scan-WY23TJCP.js.map → scan-V54HWRDY.js.map} +0 -0
- /package/dist/{service-T2L7VLTE.js.map → service-PVGTYUKX.js.map} +0 -0
- /package/dist/{static-viewer-GBR7YNF3.js.map → static-viewer-KILKIVN7.js.map} +0 -0
- /package/dist/{tokens-3BWDESVM.js.map → tokens-IXSQHPQK.js.map} +0 -0
package/src/migrate/converter.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Storybook to
|
|
2
|
+
* Storybook to Fragments Converter
|
|
3
3
|
*
|
|
4
|
-
* Transforms parsed Storybook data into
|
|
4
|
+
* Transforms parsed Storybook data into Fragment definitions.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ParsedStoryFile, ParsedArgType, ConversionResult } from "./types.js";
|
|
@@ -22,9 +22,9 @@ function sanitizeComponentName(name: string): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Convert a parsed story file into a
|
|
25
|
+
* Convert a parsed story file into a fragment definition.
|
|
26
26
|
*/
|
|
27
|
-
export function
|
|
27
|
+
export function convertToFragment(parsed: ParsedStoryFile): ConversionResult {
|
|
28
28
|
const warnings: string[] = [...parsed.warnings];
|
|
29
29
|
const todos: string[] = [];
|
|
30
30
|
|
|
@@ -39,7 +39,7 @@ export function convertToSegment(parsed: ParsedStoryFile): ConversionResult {
|
|
|
39
39
|
|
|
40
40
|
// Determine output file path for the error result
|
|
41
41
|
const outputFile = parsed.filePath
|
|
42
|
-
.replace(/\.stories\.(tsx?|jsx?|mdx)$/, ".
|
|
42
|
+
.replace(/\.stories\.(tsx?|jsx?|mdx)$/, ".fragment.tsx");
|
|
43
43
|
|
|
44
44
|
return {
|
|
45
45
|
sourceFile: parsed.filePath,
|
|
@@ -83,8 +83,8 @@ export function convertToSegment(parsed: ParsedStoryFile): ConversionResult {
|
|
|
83
83
|
.filter(v => v.needsManualReview && v.skipReason)
|
|
84
84
|
.map(v => ({ name: v.name, reason: v.skipReason! }));
|
|
85
85
|
|
|
86
|
-
// Generate the
|
|
87
|
-
const code =
|
|
86
|
+
// Generate the fragment code with _generated metadata
|
|
87
|
+
const code = generateFragmentCode({
|
|
88
88
|
componentName,
|
|
89
89
|
componentImport: parsed.meta.componentImport,
|
|
90
90
|
description: parsed.meta.description,
|
|
@@ -104,7 +104,7 @@ export function convertToSegment(parsed: ParsedStoryFile): ConversionResult {
|
|
|
104
104
|
|
|
105
105
|
// Determine output file path
|
|
106
106
|
const outputFile = parsed.filePath
|
|
107
|
-
.replace(/\.stories\.(tsx?|jsx?|mdx)$/, ".
|
|
107
|
+
.replace(/\.stories\.(tsx?|jsx?|mdx)$/, ".fragment.tsx");
|
|
108
108
|
|
|
109
109
|
return {
|
|
110
110
|
sourceFile: parsed.filePath,
|
|
@@ -122,7 +122,7 @@ export function convertToSegment(parsed: ParsedStoryFile): ConversionResult {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
* Convert Storybook argTypes to
|
|
125
|
+
* Convert Storybook argTypes to Fragments props.
|
|
126
126
|
*/
|
|
127
127
|
function convertArgTypesToProps(
|
|
128
128
|
argTypes: Record<string, ParsedArgType>
|
|
@@ -494,13 +494,13 @@ interface GenerateOptions {
|
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
/**
|
|
497
|
-
* Generate the full
|
|
497
|
+
* Generate the full fragment file code.
|
|
498
498
|
*
|
|
499
499
|
* Note: We use a placeholder component instead of importing the real component.
|
|
500
|
-
* This allows the
|
|
500
|
+
* This allows the fragment build to work without needing all component dependencies.
|
|
501
501
|
* The viewer will load the actual component at runtime.
|
|
502
502
|
*/
|
|
503
|
-
function
|
|
503
|
+
function generateFragmentCode(options: GenerateOptions): string {
|
|
504
504
|
const {
|
|
505
505
|
componentName,
|
|
506
506
|
componentImport,
|
|
@@ -549,11 +549,11 @@ ${generated.skippedVariants.map(sv => ` { name: "${escapeString(sv.name)}",
|
|
|
549
549
|
`;
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
-
// Import the actual component - this makes the
|
|
553
|
-
return `import {
|
|
552
|
+
// Import the actual component - this makes the fragment immediately usable
|
|
553
|
+
return `import { defineFragment } from "@fragments/core";
|
|
554
554
|
import { ${componentName} } from "${componentImport}";
|
|
555
555
|
|
|
556
|
-
export default
|
|
556
|
+
export default defineFragment({
|
|
557
557
|
component: ${componentName},
|
|
558
558
|
|
|
559
559
|
meta: {
|
|
@@ -626,7 +626,7 @@ function formatPropsCode(props: Record<string, PropDef>): string {
|
|
|
626
626
|
* Format variants array for code generation.
|
|
627
627
|
*
|
|
628
628
|
* Variants needing manual review get a placeholder render that won't crash.
|
|
629
|
-
* This makes the
|
|
629
|
+
* This makes the fragment file a starting point that humans can enhance.
|
|
630
630
|
*/
|
|
631
631
|
function formatVariantsCode(componentName: string, variants: VariantDef[]): string {
|
|
632
632
|
// Filter out variants that can't be rendered - they would cause runtime errors
|
package/src/migrate/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fragments/migrate
|
|
3
3
|
*
|
|
4
|
-
* Storybook to
|
|
4
|
+
* Storybook to Fragments migration tool.
|
|
5
5
|
*
|
|
6
|
-
* Provides automated conversion of Storybook CSF files to
|
|
6
|
+
* Provides automated conversion of Storybook CSF files to Fragments format,
|
|
7
7
|
* preserving component documentation while adding AI-first capabilities.
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -20,7 +20,7 @@ export {
|
|
|
20
20
|
} from "./parser.js";
|
|
21
21
|
|
|
22
22
|
// Converter
|
|
23
|
-
export {
|
|
23
|
+
export { convertToFragment } from "./converter.js";
|
|
24
24
|
|
|
25
25
|
// Report generator
|
|
26
26
|
export { generateMigrationReport } from "./report.js";
|
package/src/migrate/migrate.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Orchestrates the full migration pipeline:
|
|
5
5
|
* 1. Discover Storybook files
|
|
6
6
|
* 2. Parse CSF format
|
|
7
|
-
* 3. Convert to
|
|
7
|
+
* 3. Convert to Fragments
|
|
8
8
|
* 4. Write output files
|
|
9
9
|
* 5. Generate report
|
|
10
10
|
*/
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
ConversionResult,
|
|
19
19
|
} from "./types.js";
|
|
20
20
|
import { parseStoryFile } from "./parser.js";
|
|
21
|
-
import {
|
|
21
|
+
import { convertToFragment } from "./converter.js";
|
|
22
22
|
import { generateMigrationReport } from "./report.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -42,7 +42,7 @@ export async function migrate(options: MigrateOptions): Promise<MigrationReport>
|
|
|
42
42
|
const parsed = await parseStoryFile(storyFile);
|
|
43
43
|
|
|
44
44
|
// Convert
|
|
45
|
-
const result =
|
|
45
|
+
const result = convertToFragment(parsed);
|
|
46
46
|
|
|
47
47
|
// Determine output path
|
|
48
48
|
const outputDir = options.to ?? dirname(storyFile);
|
package/src/migrate/parser.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Storybook CSF Parser
|
|
3
3
|
*
|
|
4
4
|
* Parses Storybook Component Story Format (CSF) files and extracts
|
|
5
|
-
* structured data that can be converted to
|
|
5
|
+
* structured data that can be converted to Fragments.
|
|
6
6
|
*
|
|
7
7
|
* Supports CSF 2.0 and 3.0 formats.
|
|
8
8
|
*/
|
|
@@ -214,9 +214,9 @@ function parseMeta(
|
|
|
214
214
|
|
|
215
215
|
if (selectedTitle) {
|
|
216
216
|
result.title = selectedTitle;
|
|
217
|
-
// Extract component name from title (last
|
|
218
|
-
const
|
|
219
|
-
result.componentName =
|
|
217
|
+
// Extract component name from title (last fragment)
|
|
218
|
+
const fragments = result.title.split("/");
|
|
219
|
+
result.componentName = fragments[fragments.length - 1];
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
// Try to extract component reference
|
|
@@ -1100,13 +1100,13 @@ export function storyNameToTitle(name: string): string {
|
|
|
1100
1100
|
* Extract category from Storybook title path.
|
|
1101
1101
|
*/
|
|
1102
1102
|
export function extractCategory(title: string): string {
|
|
1103
|
-
const
|
|
1103
|
+
const fragments = title.split("/");
|
|
1104
1104
|
|
|
1105
|
-
// If we have at least 2
|
|
1105
|
+
// If we have at least 2 fragments, use the second-to-last as category
|
|
1106
1106
|
// "Components/Forms/Input" -> "forms"
|
|
1107
1107
|
// "Actions/Button" -> "actions"
|
|
1108
|
-
if (
|
|
1109
|
-
const category =
|
|
1108
|
+
if (fragments.length >= 2) {
|
|
1109
|
+
const category = fragments[fragments.length - 2];
|
|
1110
1110
|
return category.toLowerCase();
|
|
1111
1111
|
}
|
|
1112
1112
|
|
package/src/migrate/report.ts
CHANGED
|
@@ -572,7 +572,7 @@ function renderAnalyticsSection(report: MigrationReport): string {
|
|
|
572
572
|
<h2 class="section-title">Design System Analytics</h2>
|
|
573
573
|
<div class="analytics-card">
|
|
574
574
|
<p style="color: var(--text-secondary)">
|
|
575
|
-
Run <code>
|
|
575
|
+
Run <code>fragments build && fragments analyze</code> after migration to see design system analytics.
|
|
576
576
|
</p>
|
|
577
577
|
</div>
|
|
578
578
|
</section>
|
|
@@ -618,7 +618,7 @@ function renderFooter(): string {
|
|
|
618
618
|
return `
|
|
619
619
|
<footer class="footer">
|
|
620
620
|
<p>Generated by <a href="#">${BRAND.name}</a> Migration Tool</p>
|
|
621
|
-
<p style="margin-top: 8px">Run <code>
|
|
621
|
+
<p style="margin-top: 8px">Run <code>fragments dev</code> to view your new design system documentation</p>
|
|
622
622
|
</footer>
|
|
623
623
|
`;
|
|
624
624
|
}
|
package/src/migrate/types.ts
CHANGED
|
@@ -94,9 +94,9 @@ export interface StorybookConfig {
|
|
|
94
94
|
export interface ConversionResult {
|
|
95
95
|
/** Original story file path */
|
|
96
96
|
sourceFile: string;
|
|
97
|
-
/** Output
|
|
97
|
+
/** Output fragment file path */
|
|
98
98
|
outputFile: string;
|
|
99
|
-
/** Generated
|
|
99
|
+
/** Generated fragment code */
|
|
100
100
|
code: string;
|
|
101
101
|
/** Component name */
|
|
102
102
|
componentName: string;
|
|
@@ -140,7 +140,7 @@ export interface MigrationReport {
|
|
|
140
140
|
results: ConversionResult[];
|
|
141
141
|
/** Categories discovered */
|
|
142
142
|
categories: string[];
|
|
143
|
-
/** Analytics from the converted
|
|
143
|
+
/** Analytics from the converted fragments */
|
|
144
144
|
analytics?: {
|
|
145
145
|
overallScore: number;
|
|
146
146
|
coveragePercentage: number;
|
|
@@ -158,7 +158,7 @@ export interface MigrateOptions {
|
|
|
158
158
|
to?: string;
|
|
159
159
|
/** Dry run - don't write files */
|
|
160
160
|
dryRun?: boolean;
|
|
161
|
-
/** Overwrite existing
|
|
161
|
+
/** Overwrite existing fragment files */
|
|
162
162
|
overwrite?: boolean;
|
|
163
163
|
/** Generate HTML report */
|
|
164
164
|
report?: boolean;
|
package/src/screenshot.ts
CHANGED
|
@@ -2,11 +2,11 @@ import pc from 'picocolors';
|
|
|
2
2
|
import {
|
|
3
3
|
BRAND,
|
|
4
4
|
DEFAULTS,
|
|
5
|
-
type
|
|
6
|
-
type
|
|
5
|
+
type FragmentsConfig,
|
|
6
|
+
type FragmentDefinition,
|
|
7
7
|
type Theme,
|
|
8
8
|
} from './core/index.js';
|
|
9
|
-
import {
|
|
9
|
+
import { discoverFragmentFiles, loadFragmentFile } from './core/node.js';
|
|
10
10
|
import {
|
|
11
11
|
BrowserPool,
|
|
12
12
|
CaptureEngine,
|
|
@@ -56,7 +56,7 @@ export interface ScreenshotResult {
|
|
|
56
56
|
* Execute the screenshot command
|
|
57
57
|
*/
|
|
58
58
|
export async function runScreenshotCommand(
|
|
59
|
-
config:
|
|
59
|
+
config: FragmentsConfig,
|
|
60
60
|
configDir: string,
|
|
61
61
|
options: ScreenshotCommandOptions = {}
|
|
62
62
|
): Promise<ScreenshotResult> {
|
|
@@ -72,11 +72,11 @@ export async function runScreenshotCommand(
|
|
|
72
72
|
});
|
|
73
73
|
await storage.initialize();
|
|
74
74
|
|
|
75
|
-
// Discover
|
|
76
|
-
const
|
|
75
|
+
// Discover fragments
|
|
76
|
+
const fragmentFiles = await discoverFragmentFiles(config, configDir);
|
|
77
77
|
|
|
78
|
-
if (
|
|
79
|
-
console.log(pc.yellow('No
|
|
78
|
+
if (fragmentFiles.length === 0) {
|
|
79
|
+
console.log(pc.yellow('No fragment files found.'));
|
|
80
80
|
return {
|
|
81
81
|
success: true,
|
|
82
82
|
captured: 0,
|
|
@@ -86,14 +86,14 @@ export async function runScreenshotCommand(
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// Load all
|
|
90
|
-
const
|
|
89
|
+
// Load all fragments
|
|
90
|
+
const fragments: Array<{ path: string; fragment: FragmentDefinition }> = [];
|
|
91
91
|
|
|
92
|
-
for (const file of
|
|
92
|
+
for (const file of fragmentFiles) {
|
|
93
93
|
try {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
|
|
94
|
+
const fragment = await loadFragmentFile(file.absolutePath);
|
|
95
|
+
if (fragment) {
|
|
96
|
+
fragments.push({ path: file.relativePath, fragment });
|
|
97
97
|
}
|
|
98
98
|
} catch (error) {
|
|
99
99
|
errors.push({
|
|
@@ -105,11 +105,11 @@ export async function runScreenshotCommand(
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// Filter by component if specified
|
|
108
|
-
const
|
|
109
|
-
?
|
|
110
|
-
:
|
|
108
|
+
const filteredFragments = options.component
|
|
109
|
+
? fragments.filter((s) => s.fragment.meta.name === options.component)
|
|
110
|
+
: fragments;
|
|
111
111
|
|
|
112
|
-
if (options.component &&
|
|
112
|
+
if (options.component && filteredFragments.length === 0) {
|
|
113
113
|
console.log(pc.yellow(`Component "${options.component}" not found.`));
|
|
114
114
|
return {
|
|
115
115
|
success: false,
|
|
@@ -127,14 +127,14 @@ export async function runScreenshotCommand(
|
|
|
127
127
|
render: () => unknown;
|
|
128
128
|
}> = [];
|
|
129
129
|
|
|
130
|
-
for (const {
|
|
130
|
+
for (const { fragment } of filteredFragments) {
|
|
131
131
|
const variants = options.variant
|
|
132
|
-
?
|
|
133
|
-
:
|
|
132
|
+
? fragment.variants.filter((v) => v.name === options.variant)
|
|
133
|
+
: fragment.variants;
|
|
134
134
|
|
|
135
135
|
for (const variant of variants) {
|
|
136
136
|
variantsToCapture.push({
|
|
137
|
-
component:
|
|
137
|
+
component: fragment.meta.name,
|
|
138
138
|
variant: variant.name,
|
|
139
139
|
render: variant.render,
|
|
140
140
|
});
|
|
@@ -9,7 +9,7 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import {
|
|
10
10
|
extractPropsFromSource,
|
|
11
11
|
extractPropsFromFile,
|
|
12
|
-
|
|
12
|
+
convertToFragmentProps,
|
|
13
13
|
} from "../enhance/props-extractor.js";
|
|
14
14
|
|
|
15
15
|
let tempDir: string;
|
|
@@ -331,8 +331,8 @@ export function MyComponent(props: MyComponentProps) {
|
|
|
331
331
|
});
|
|
332
332
|
});
|
|
333
333
|
|
|
334
|
-
describe("
|
|
335
|
-
it("should convert extracted props to
|
|
334
|
+
describe("convertToFragmentProps", () => {
|
|
335
|
+
it("should convert extracted props to fragment format", () => {
|
|
336
336
|
const source = `
|
|
337
337
|
interface ButtonProps {
|
|
338
338
|
/** Button variant */
|
|
@@ -346,20 +346,20 @@ interface ButtonProps {
|
|
|
346
346
|
}
|
|
347
347
|
`;
|
|
348
348
|
const result = extractPropsFromSource(source, "Button.tsx");
|
|
349
|
-
const
|
|
349
|
+
const fragmentProps = convertToFragmentProps(result.props);
|
|
350
350
|
|
|
351
|
-
expect(
|
|
352
|
-
expect(
|
|
353
|
-
expect(
|
|
354
|
-
expect(
|
|
351
|
+
expect(fragmentProps).toHaveProperty("variant");
|
|
352
|
+
expect(fragmentProps.variant.type).toBe("enum");
|
|
353
|
+
expect(fragmentProps.variant.values).toEqual(["primary", "secondary"]);
|
|
354
|
+
expect(fragmentProps.variant.description).toBe("Button variant");
|
|
355
355
|
|
|
356
|
-
expect(
|
|
357
|
-
expect(
|
|
358
|
-
expect(
|
|
359
|
-
expect(
|
|
356
|
+
expect(fragmentProps).toHaveProperty("disabled");
|
|
357
|
+
expect(fragmentProps.disabled.type).toBe("boolean");
|
|
358
|
+
expect(fragmentProps.disabled.required).toBe(false);
|
|
359
|
+
expect(fragmentProps.disabled.default).toBe(false);
|
|
360
360
|
|
|
361
|
-
expect(
|
|
362
|
-
expect(
|
|
363
|
-
expect(
|
|
361
|
+
expect(fragmentProps).toHaveProperty("onClick");
|
|
362
|
+
expect(fragmentProps.onClick.type).toBe("function");
|
|
363
|
+
expect(fragmentProps.onClick.required).toBe(true);
|
|
364
364
|
});
|
|
365
365
|
});
|
package/src/service/analytics.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Analytics engine for design system insights.
|
|
3
3
|
*
|
|
4
|
-
* Analyzes compiled
|
|
4
|
+
* Analyzes compiled fragments to provide:
|
|
5
5
|
* - Component inventory stats
|
|
6
6
|
* - Documentation coverage
|
|
7
7
|
* - Usage pattern insights
|
|
8
8
|
* - Quality scores
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type { CompiledFragmentsFile, CompiledFragment } from "../core/index.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Overall design system analytics
|
|
@@ -157,25 +157,25 @@ export interface Recommendation {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
|
-
* Analyze a compiled
|
|
160
|
+
* Analyze a compiled fragments file and produce analytics
|
|
161
161
|
*/
|
|
162
162
|
export function analyzeDesignSystem(
|
|
163
|
-
data:
|
|
163
|
+
data: CompiledFragmentsFile
|
|
164
164
|
): DesignSystemAnalytics {
|
|
165
|
-
const
|
|
165
|
+
const fragments = Object.values(data.fragments);
|
|
166
166
|
const analyzedAt = new Date();
|
|
167
167
|
|
|
168
168
|
// Build component summaries
|
|
169
|
-
const summaries =
|
|
169
|
+
const summaries = fragments.map((s) => buildComponentSummary(s));
|
|
170
170
|
|
|
171
171
|
// Calculate coverage
|
|
172
|
-
const coverage = calculateCoverage(
|
|
172
|
+
const coverage = calculateCoverage(fragments, summaries);
|
|
173
173
|
|
|
174
174
|
// Calculate quality insights
|
|
175
|
-
const quality = calculateQuality(
|
|
175
|
+
const quality = calculateQuality(fragments, summaries);
|
|
176
176
|
|
|
177
177
|
// Build distribution data
|
|
178
|
-
const distribution = buildDistribution(
|
|
178
|
+
const distribution = buildDistribution(fragments, summaries);
|
|
179
179
|
|
|
180
180
|
// Build inventory
|
|
181
181
|
const inventory = buildInventory(summaries);
|
|
@@ -187,14 +187,14 @@ export function analyzeDesignSystem(
|
|
|
187
187
|
const overallScore = calculateOverallScore(coverage, quality, summaries);
|
|
188
188
|
|
|
189
189
|
// Collect all categories
|
|
190
|
-
const categories = [...new Set(
|
|
190
|
+
const categories = [...new Set(fragments.map((s) => s.meta.category))].sort();
|
|
191
191
|
|
|
192
192
|
return {
|
|
193
193
|
analyzedAt,
|
|
194
194
|
summary: {
|
|
195
|
-
totalComponents:
|
|
196
|
-
totalVariants:
|
|
197
|
-
totalProps:
|
|
195
|
+
totalComponents: fragments.length,
|
|
196
|
+
totalVariants: fragments.reduce((sum, s) => sum + s.variants.length, 0),
|
|
197
|
+
totalProps: fragments.reduce(
|
|
198
198
|
(sum, s) => sum + Object.keys(s.props ?? {}).length,
|
|
199
199
|
0
|
|
200
200
|
),
|
|
@@ -212,12 +212,12 @@ export function analyzeDesignSystem(
|
|
|
212
212
|
/**
|
|
213
213
|
* Build summary for a single component
|
|
214
214
|
*/
|
|
215
|
-
function buildComponentSummary(
|
|
216
|
-
const propCount = Object.keys(
|
|
217
|
-
const hasUsageWhen = (
|
|
218
|
-
const hasUsageWhenNot = (
|
|
219
|
-
const hasGuidelines = (
|
|
220
|
-
const hasRelations = (
|
|
215
|
+
function buildComponentSummary(fragment: CompiledFragment): ComponentSummary {
|
|
216
|
+
const propCount = Object.keys(fragment.props ?? {}).length;
|
|
217
|
+
const hasUsageWhen = (fragment.usage?.when?.length ?? 0) > 0;
|
|
218
|
+
const hasUsageWhenNot = (fragment.usage?.whenNot?.length ?? 0) > 0;
|
|
219
|
+
const hasGuidelines = (fragment.usage?.guidelines?.length ?? 0) > 0;
|
|
220
|
+
const hasRelations = (fragment.relations?.length ?? 0) > 0;
|
|
221
221
|
|
|
222
222
|
// Calculate documentation score
|
|
223
223
|
let docScore = 0;
|
|
@@ -225,7 +225,7 @@ function buildComponentSummary(segment: CompiledSegment): ComponentSummary {
|
|
|
225
225
|
|
|
226
226
|
// Description (20 points)
|
|
227
227
|
docTotal += 20;
|
|
228
|
-
if (
|
|
228
|
+
if (fragment.meta.description && fragment.meta.description.length > 20) {
|
|
229
229
|
docScore += 20;
|
|
230
230
|
}
|
|
231
231
|
|
|
@@ -248,7 +248,7 @@ function buildComponentSummary(segment: CompiledSegment): ComponentSummary {
|
|
|
248
248
|
// Props documented (15 points)
|
|
249
249
|
docTotal += 15;
|
|
250
250
|
if (propCount > 0) {
|
|
251
|
-
const documentedProps = Object.values(
|
|
251
|
+
const documentedProps = Object.values(fragment.props ?? {}).filter(
|
|
252
252
|
(p) => p.description && p.description.length > 5
|
|
253
253
|
).length;
|
|
254
254
|
docScore += Math.round((documentedProps / propCount) * 15);
|
|
@@ -257,10 +257,10 @@ function buildComponentSummary(segment: CompiledSegment): ComponentSummary {
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
return {
|
|
260
|
-
name:
|
|
261
|
-
category:
|
|
262
|
-
status:
|
|
263
|
-
variantCount:
|
|
260
|
+
name: fragment.meta.name,
|
|
261
|
+
category: fragment.meta.category,
|
|
262
|
+
status: fragment.meta.status ?? "stable",
|
|
263
|
+
variantCount: fragment.variants.length,
|
|
264
264
|
propCount,
|
|
265
265
|
hasUsageWhen,
|
|
266
266
|
hasUsageWhenNot,
|
|
@@ -274,14 +274,14 @@ function buildComponentSummary(segment: CompiledSegment): ComponentSummary {
|
|
|
274
274
|
* Calculate coverage analytics
|
|
275
275
|
*/
|
|
276
276
|
function calculateCoverage(
|
|
277
|
-
|
|
277
|
+
fragments: CompiledFragment[],
|
|
278
278
|
summaries: ComponentSummary[]
|
|
279
279
|
): CoverageAnalytics {
|
|
280
|
-
const total =
|
|
280
|
+
const total = fragments.length;
|
|
281
281
|
|
|
282
282
|
const fields = {
|
|
283
283
|
description: {
|
|
284
|
-
covered:
|
|
284
|
+
covered: fragments.filter(
|
|
285
285
|
(s) => s.meta.description && s.meta.description.length > 10
|
|
286
286
|
).length,
|
|
287
287
|
total,
|
|
@@ -303,7 +303,7 @@ function calculateCoverage(
|
|
|
303
303
|
percentage: 0,
|
|
304
304
|
},
|
|
305
305
|
accessibility: {
|
|
306
|
-
covered:
|
|
306
|
+
covered: fragments.filter((s) => (s.usage?.accessibility?.length ?? 0) > 0)
|
|
307
307
|
.length,
|
|
308
308
|
total,
|
|
309
309
|
percentage: 0,
|
|
@@ -326,8 +326,8 @@ function calculateCoverage(
|
|
|
326
326
|
};
|
|
327
327
|
|
|
328
328
|
// Calculate prop coverage
|
|
329
|
-
for (const
|
|
330
|
-
const props = Object.values(
|
|
329
|
+
for (const fragment of fragments) {
|
|
330
|
+
const props = Object.values(fragment.props ?? {});
|
|
331
331
|
fields.propDescriptions.total += props.length;
|
|
332
332
|
fields.propConstraints.total += props.length;
|
|
333
333
|
|
|
@@ -377,7 +377,7 @@ function calculateCoverage(
|
|
|
377
377
|
* Calculate quality analytics
|
|
378
378
|
*/
|
|
379
379
|
function calculateQuality(
|
|
380
|
-
|
|
380
|
+
fragments: CompiledFragment[],
|
|
381
381
|
summaries: ComponentSummary[]
|
|
382
382
|
): QualityAnalytics {
|
|
383
383
|
const missingWhenNot = summaries
|
|
@@ -399,10 +399,10 @@ function calculateQuality(
|
|
|
399
399
|
const undocumentedProps: QualityAnalytics["undocumentedProps"] = [];
|
|
400
400
|
const unconstrainedProps: QualityAnalytics["unconstrainedProps"] = [];
|
|
401
401
|
|
|
402
|
-
for (const
|
|
403
|
-
for (const [propName, prop] of Object.entries(
|
|
402
|
+
for (const fragment of fragments) {
|
|
403
|
+
for (const [propName, prop] of Object.entries(fragment.props ?? {})) {
|
|
404
404
|
if (!prop.description || prop.description.length < 5) {
|
|
405
|
-
undocumentedProps.push({ component:
|
|
405
|
+
undocumentedProps.push({ component: fragment.meta.name, prop: propName });
|
|
406
406
|
}
|
|
407
407
|
if (
|
|
408
408
|
(prop.constraints?.length ?? 0) === 0 &&
|
|
@@ -410,7 +410,7 @@ function calculateQuality(
|
|
|
410
410
|
prop.type !== "function"
|
|
411
411
|
) {
|
|
412
412
|
unconstrainedProps.push({
|
|
413
|
-
component:
|
|
413
|
+
component: fragment.meta.name,
|
|
414
414
|
prop: propName,
|
|
415
415
|
});
|
|
416
416
|
}
|
|
@@ -431,7 +431,7 @@ function calculateQuality(
|
|
|
431
431
|
* Build distribution data for charts
|
|
432
432
|
*/
|
|
433
433
|
function buildDistribution(
|
|
434
|
-
|
|
434
|
+
fragments: CompiledFragment[],
|
|
435
435
|
summaries: ComponentSummary[]
|
|
436
436
|
): DistributionAnalytics {
|
|
437
437
|
// Variants per component
|
|
@@ -464,8 +464,8 @@ function buildDistribution(
|
|
|
464
464
|
|
|
465
465
|
// Tag frequency
|
|
466
466
|
const tagMap = new Map<string, number>();
|
|
467
|
-
for (const
|
|
468
|
-
for (const tag of
|
|
467
|
+
for (const fragment of fragments) {
|
|
468
|
+
for (const tag of fragment.meta.tags ?? []) {
|
|
469
469
|
tagMap.set(tag, (tagMap.get(tag) ?? 0) + 1);
|
|
470
470
|
}
|
|
471
471
|
}
|
|
@@ -559,9 +559,9 @@ function inferComponentName(filePath: string): string {
|
|
|
559
559
|
}
|
|
560
560
|
|
|
561
561
|
/**
|
|
562
|
-
* Convert extracted props to PropDefinition format for
|
|
562
|
+
* Convert extracted props to PropDefinition format for fragments.json
|
|
563
563
|
*/
|
|
564
|
-
export function
|
|
564
|
+
export function convertToFragmentProps(
|
|
565
565
|
props: ExtractedProp[]
|
|
566
566
|
): Record<string, PropDefinition> {
|
|
567
567
|
const result: Record<string, PropDefinition> = {};
|
|
@@ -290,13 +290,13 @@ export interface ExtractedDocs {
|
|
|
290
290
|
/** JSDoc tags (deprecated, since, version, etc.) */
|
|
291
291
|
tags: Record<string, string>;
|
|
292
292
|
|
|
293
|
-
/** Existing when/whenNot from
|
|
293
|
+
/** Existing when/whenNot from fragment file */
|
|
294
294
|
existingWhen?: string[];
|
|
295
295
|
existingWhenNot?: string[];
|
|
296
296
|
existingGuidelines?: string[];
|
|
297
297
|
|
|
298
298
|
/** Source of documentation */
|
|
299
|
-
sources?: ("jsdoc" | "storybook" | "markdown" | "
|
|
299
|
+
sources?: ("jsdoc" | "storybook" | "markdown" | "fragment")[];
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
/**
|
package/src/service/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fragments/service
|
|
3
3
|
*
|
|
4
|
-
* Screenshot capture and verification service for
|
|
4
|
+
* Screenshot capture and verification service for Fragments.
|
|
5
5
|
* Provides browser pool management, screenshot capture, and visual diff capabilities.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -203,7 +203,7 @@ export {
|
|
|
203
203
|
extractPropsFromSource,
|
|
204
204
|
extractPropsForComponent,
|
|
205
205
|
extractAllComponentProps,
|
|
206
|
-
|
|
206
|
+
convertToFragmentProps,
|
|
207
207
|
type ExtractedProp,
|
|
208
208
|
type PropsExtractionResult,
|
|
209
209
|
type PropsExtractionOptions,
|