@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
package/src/diff.ts
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import {
|
|
3
|
+
BRAND,
|
|
4
|
+
DEFAULTS,
|
|
5
|
+
type SegmentsConfig,
|
|
6
|
+
type SegmentDefinition,
|
|
7
|
+
type Theme,
|
|
8
|
+
} from './core/index.js';
|
|
9
|
+
import { discoverSegmentFiles, loadSegmentFile } from './core/node.js';
|
|
10
|
+
import {
|
|
11
|
+
BrowserPool,
|
|
12
|
+
CaptureEngine,
|
|
13
|
+
StorageManager,
|
|
14
|
+
DiffEngine,
|
|
15
|
+
formatMs,
|
|
16
|
+
type CaptureOptions,
|
|
17
|
+
type DiffResult,
|
|
18
|
+
} from './service/index.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for the diff command
|
|
22
|
+
*/
|
|
23
|
+
export interface DiffCommandOptions {
|
|
24
|
+
/** Specific component to diff */
|
|
25
|
+
component?: string;
|
|
26
|
+
|
|
27
|
+
/** Specific variant to diff */
|
|
28
|
+
variant?: string;
|
|
29
|
+
|
|
30
|
+
/** Theme to compare */
|
|
31
|
+
theme?: Theme;
|
|
32
|
+
|
|
33
|
+
/** CI mode - exit 1 on differences */
|
|
34
|
+
ci?: boolean;
|
|
35
|
+
|
|
36
|
+
/** Diff threshold percentage */
|
|
37
|
+
threshold?: number;
|
|
38
|
+
|
|
39
|
+
/** Open diff images */
|
|
40
|
+
open?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Single diff result with metadata
|
|
45
|
+
*/
|
|
46
|
+
export interface VariantDiffResult {
|
|
47
|
+
component: string;
|
|
48
|
+
variant: string;
|
|
49
|
+
theme: Theme;
|
|
50
|
+
result: DiffResult;
|
|
51
|
+
diffImagePath?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Result of the diff command
|
|
56
|
+
*/
|
|
57
|
+
export interface DiffCommandResult {
|
|
58
|
+
success: boolean;
|
|
59
|
+
total: number;
|
|
60
|
+
passed: number;
|
|
61
|
+
failed: number;
|
|
62
|
+
missing: number;
|
|
63
|
+
results: VariantDiffResult[];
|
|
64
|
+
totalTimeMs: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute the diff command
|
|
69
|
+
*/
|
|
70
|
+
export async function runDiffCommand(
|
|
71
|
+
config: SegmentsConfig,
|
|
72
|
+
configDir: string,
|
|
73
|
+
options: DiffCommandOptions = {}
|
|
74
|
+
): Promise<DiffCommandResult> {
|
|
75
|
+
const startTime = Date.now();
|
|
76
|
+
const results: VariantDiffResult[] = [];
|
|
77
|
+
|
|
78
|
+
// Initialize storage
|
|
79
|
+
const storage = new StorageManager({
|
|
80
|
+
projectRoot: configDir,
|
|
81
|
+
viewport: config.screenshots?.viewport,
|
|
82
|
+
});
|
|
83
|
+
await storage.initialize();
|
|
84
|
+
|
|
85
|
+
// Initialize diff engine
|
|
86
|
+
const threshold = options.threshold ?? config.screenshots?.threshold ?? DEFAULTS.diffThreshold;
|
|
87
|
+
const diffEngine = new DiffEngine(threshold);
|
|
88
|
+
|
|
89
|
+
// Discover segments
|
|
90
|
+
const segmentFiles = await discoverSegmentFiles(config, configDir);
|
|
91
|
+
|
|
92
|
+
if (segmentFiles.length === 0) {
|
|
93
|
+
console.log(pc.yellow('No segment files found.'));
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
total: 0,
|
|
97
|
+
passed: 0,
|
|
98
|
+
failed: 0,
|
|
99
|
+
missing: 0,
|
|
100
|
+
results: [],
|
|
101
|
+
totalTimeMs: Date.now() - startTime,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Load all segments
|
|
106
|
+
const segments: Array<{ path: string; segment: SegmentDefinition }> = [];
|
|
107
|
+
|
|
108
|
+
for (const file of segmentFiles) {
|
|
109
|
+
try {
|
|
110
|
+
const segment = await loadSegmentFile(file.absolutePath);
|
|
111
|
+
if (segment) {
|
|
112
|
+
segments.push({ path: file.relativePath, segment });
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// Skip failed loads
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Filter by component if specified
|
|
120
|
+
const filteredSegments = options.component
|
|
121
|
+
? segments.filter((s) => s.segment.meta.name === options.component)
|
|
122
|
+
: segments;
|
|
123
|
+
|
|
124
|
+
if (options.component && filteredSegments.length === 0) {
|
|
125
|
+
console.log(pc.yellow(`Component "${options.component}" not found.`));
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
total: 0,
|
|
129
|
+
passed: 0,
|
|
130
|
+
failed: 0,
|
|
131
|
+
missing: 0,
|
|
132
|
+
results: [],
|
|
133
|
+
totalTimeMs: Date.now() - startTime,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Build list of variants to diff
|
|
138
|
+
const variantsToDiff: Array<{
|
|
139
|
+
component: string;
|
|
140
|
+
variant: string;
|
|
141
|
+
}> = [];
|
|
142
|
+
|
|
143
|
+
for (const { segment } of filteredSegments) {
|
|
144
|
+
const variants = options.variant
|
|
145
|
+
? segment.variants.filter((v) => v.name === options.variant)
|
|
146
|
+
: segment.variants;
|
|
147
|
+
|
|
148
|
+
for (const variant of variants) {
|
|
149
|
+
variantsToDiff.push({
|
|
150
|
+
component: segment.meta.name,
|
|
151
|
+
variant: variant.name,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (variantsToDiff.length === 0) {
|
|
157
|
+
console.log(pc.yellow('No variants to compare.'));
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
total: 0,
|
|
161
|
+
passed: 0,
|
|
162
|
+
failed: 0,
|
|
163
|
+
missing: 0,
|
|
164
|
+
results: [],
|
|
165
|
+
totalTimeMs: Date.now() - startTime,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Determine theme
|
|
170
|
+
const theme: Theme = options.theme ?? DEFAULTS.theme;
|
|
171
|
+
const viewport = config.screenshots?.viewport ?? DEFAULTS.viewport;
|
|
172
|
+
|
|
173
|
+
console.log(pc.cyan(`\n${BRAND.name} Diff\n`));
|
|
174
|
+
console.log(pc.dim(`Comparing against baselines (theme: ${theme}, threshold: ${threshold}%):\n`));
|
|
175
|
+
|
|
176
|
+
// Initialize browser pool
|
|
177
|
+
const pool = new BrowserPool({
|
|
178
|
+
viewport,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Create capture engine
|
|
182
|
+
const viewerPort = DEFAULTS.port;
|
|
183
|
+
const baseUrl = `http://localhost:${viewerPort}`;
|
|
184
|
+
const captureEngine = new CaptureEngine(pool, baseUrl);
|
|
185
|
+
|
|
186
|
+
let passed = 0;
|
|
187
|
+
let failed = 0;
|
|
188
|
+
let missing = 0;
|
|
189
|
+
|
|
190
|
+
const captureOptions: CaptureOptions = {
|
|
191
|
+
theme,
|
|
192
|
+
viewport,
|
|
193
|
+
delay: config.screenshots?.delay ?? DEFAULTS.captureDelayMs,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
// Warm up the pool
|
|
198
|
+
await pool.warmup();
|
|
199
|
+
|
|
200
|
+
// Compare each variant
|
|
201
|
+
for (const { component, variant } of variantsToDiff) {
|
|
202
|
+
// Load baseline
|
|
203
|
+
const baseline = await storage.loadBaseline(component, variant, theme);
|
|
204
|
+
|
|
205
|
+
if (!baseline) {
|
|
206
|
+
console.log(
|
|
207
|
+
` ${pc.yellow('?')} ${component}/${variant} ${pc.dim('(no baseline)')}`
|
|
208
|
+
);
|
|
209
|
+
missing++;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
// Capture current
|
|
215
|
+
const current = await captureEngine.captureVariant(
|
|
216
|
+
component,
|
|
217
|
+
variant,
|
|
218
|
+
captureOptions
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// Quick hash check
|
|
222
|
+
if (diffEngine.areIdentical(current, baseline)) {
|
|
223
|
+
console.log(` ${pc.green('✓')} ${component}/${variant} ${pc.dim('0.0%')}`);
|
|
224
|
+
results.push({
|
|
225
|
+
component,
|
|
226
|
+
variant,
|
|
227
|
+
theme,
|
|
228
|
+
result: {
|
|
229
|
+
matches: true,
|
|
230
|
+
diffPercentage: 0,
|
|
231
|
+
diffPixelCount: 0,
|
|
232
|
+
totalPixels: current.viewport.width * current.viewport.height,
|
|
233
|
+
changedRegions: [],
|
|
234
|
+
diffTimeMs: 0,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
passed++;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Full diff
|
|
242
|
+
const diffResult = diffEngine.compare(current, baseline, { threshold });
|
|
243
|
+
|
|
244
|
+
if (diffResult.matches) {
|
|
245
|
+
console.log(
|
|
246
|
+
` ${pc.green('✓')} ${component}/${variant} ${pc.dim(`${diffResult.diffPercentage}%`)}`
|
|
247
|
+
);
|
|
248
|
+
passed++;
|
|
249
|
+
} else {
|
|
250
|
+
// Save diff image
|
|
251
|
+
let diffImagePath: string | undefined;
|
|
252
|
+
if (diffResult.diffImage) {
|
|
253
|
+
diffImagePath = await storage.saveDiff(
|
|
254
|
+
component,
|
|
255
|
+
variant,
|
|
256
|
+
theme,
|
|
257
|
+
diffResult.diffImage
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
console.log(
|
|
262
|
+
` ${pc.red('✗')} ${component}/${variant} ${pc.yellow(`${diffResult.diffPercentage}%`)}` +
|
|
263
|
+
(diffImagePath ? pc.dim(` → ${diffImagePath}`) : '')
|
|
264
|
+
);
|
|
265
|
+
failed++;
|
|
266
|
+
|
|
267
|
+
results.push({
|
|
268
|
+
component,
|
|
269
|
+
variant,
|
|
270
|
+
theme,
|
|
271
|
+
result: diffResult,
|
|
272
|
+
diffImagePath,
|
|
273
|
+
});
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
results.push({
|
|
278
|
+
component,
|
|
279
|
+
variant,
|
|
280
|
+
theme,
|
|
281
|
+
result: diffResult,
|
|
282
|
+
});
|
|
283
|
+
} catch (error) {
|
|
284
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
285
|
+
console.log(` ${pc.red('!')} ${component}/${variant} ${pc.dim(errorMsg)}`);
|
|
286
|
+
failed++;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} finally {
|
|
290
|
+
// Shutdown browser pool
|
|
291
|
+
await pool.shutdown();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const totalTimeMs = Date.now() - startTime;
|
|
295
|
+
const total = passed + failed + missing;
|
|
296
|
+
|
|
297
|
+
// Print summary
|
|
298
|
+
console.log();
|
|
299
|
+
if (failed === 0 && missing === 0) {
|
|
300
|
+
console.log(pc.green(`✓ All ${passed} variant(s) match baselines`));
|
|
301
|
+
} else if (failed > 0) {
|
|
302
|
+
console.log(pc.red(`✗ ${failed} variant(s) differ from baselines`));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (missing > 0) {
|
|
306
|
+
console.log(pc.yellow(` ${missing} variant(s) have no baseline (run \`${BRAND.cliCommand} screenshot\`)`));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log(pc.dim(` Completed in ${formatMs(totalTimeMs)}\n`));
|
|
310
|
+
|
|
311
|
+
// In CI mode, exit with error if any failed
|
|
312
|
+
const success = failed === 0;
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
success,
|
|
316
|
+
total,
|
|
317
|
+
passed,
|
|
318
|
+
failed,
|
|
319
|
+
missing,
|
|
320
|
+
results,
|
|
321
|
+
totalTimeMs,
|
|
322
|
+
};
|
|
323
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Re-export from core/node (Node.js-only APIs)
|
|
2
|
+
export {
|
|
3
|
+
loadConfig,
|
|
4
|
+
findConfigFile,
|
|
5
|
+
discoverSegmentFiles,
|
|
6
|
+
discoverComponentFiles,
|
|
7
|
+
extractComponentName,
|
|
8
|
+
} from "./core/node.js";
|
|
9
|
+
export type { DiscoveredFile } from "./core/node.js";
|
|
10
|
+
|
|
11
|
+
// Validators
|
|
12
|
+
export { validateSchema, validateCoverage, validateAll } from "./validators.js";
|
|
13
|
+
export type {
|
|
14
|
+
ValidationResult,
|
|
15
|
+
ValidationError,
|
|
16
|
+
ValidationWarning,
|
|
17
|
+
} from "./validators.js";
|
|
18
|
+
|
|
19
|
+
// Build
|
|
20
|
+
export { buildSegments } from "./build.js";
|
|
21
|
+
export type { BuildResult } from "./build.js";
|
|
22
|
+
|
|
23
|
+
// Screenshot
|
|
24
|
+
export { runScreenshotCommand } from "./screenshot.js";
|
|
25
|
+
export type {
|
|
26
|
+
ScreenshotCommandOptions,
|
|
27
|
+
ScreenshotResult,
|
|
28
|
+
} from "./screenshot.js";
|
|
29
|
+
|
|
30
|
+
// Diff
|
|
31
|
+
export { runDiffCommand } from "./diff.js";
|
|
32
|
+
export type {
|
|
33
|
+
DiffCommandOptions,
|
|
34
|
+
DiffCommandResult,
|
|
35
|
+
VariantDiffResult,
|
|
36
|
+
} from "./diff.js";
|
|
37
|
+
|
|
38
|
+
// Analyze
|
|
39
|
+
export { runAnalyzeCommand } from "./analyze.js";
|
|
40
|
+
export type { AnalyzeOptions, AnalyzeResult } from "./analyze.js";
|
|
41
|
+
|
|
42
|
+
// Static Viewer
|
|
43
|
+
export { generateStaticViewer, generateViewerFromJson } from "./static-viewer.js";
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { projectFields } from "../utils.js";
|
|
3
|
+
|
|
4
|
+
describe("projectFields", () => {
|
|
5
|
+
describe("basic field extraction", () => {
|
|
6
|
+
it("returns full object when no fields specified", () => {
|
|
7
|
+
const obj = { a: 1, b: 2, c: 3 };
|
|
8
|
+
expect(projectFields(obj, [])).toEqual(obj);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("extracts single top-level field", () => {
|
|
12
|
+
const obj = { name: "Button", category: "actions", status: "stable" };
|
|
13
|
+
const result = projectFields(obj, ["name"]);
|
|
14
|
+
expect(result).toEqual({ name: "Button" });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("extracts multiple top-level fields", () => {
|
|
18
|
+
const obj = { name: "Button", category: "actions", status: "stable" };
|
|
19
|
+
const result = projectFields(obj, ["name", "category"]);
|
|
20
|
+
expect(result).toEqual({ name: "Button", category: "actions" });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("ignores non-existent fields", () => {
|
|
24
|
+
const obj = { name: "Button" };
|
|
25
|
+
const result = projectFields(obj, ["name", "nonexistent"]);
|
|
26
|
+
expect(result).toEqual({ name: "Button" });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("nested field extraction", () => {
|
|
31
|
+
it("extracts nested field with dot notation", () => {
|
|
32
|
+
const obj = {
|
|
33
|
+
meta: { name: "Button", category: "actions" },
|
|
34
|
+
usage: { when: ["action needed"], whenNot: ["static text"] },
|
|
35
|
+
};
|
|
36
|
+
const result = projectFields(obj, ["meta.name"]);
|
|
37
|
+
expect(result).toEqual({ meta: { name: "Button" } });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("extracts multiple nested fields from same parent", () => {
|
|
41
|
+
const obj = {
|
|
42
|
+
meta: { name: "Button", category: "actions", status: "stable" },
|
|
43
|
+
};
|
|
44
|
+
const result = projectFields(obj, ["meta.name", "meta.category"]);
|
|
45
|
+
expect(result).toEqual({ meta: { name: "Button", category: "actions" } });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("extracts deeply nested fields", () => {
|
|
49
|
+
const obj = {
|
|
50
|
+
config: {
|
|
51
|
+
theme: {
|
|
52
|
+
colors: { primary: "blue", secondary: "gray" },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const result = projectFields(obj, ["config.theme.colors.primary"]);
|
|
57
|
+
expect(result).toEqual({
|
|
58
|
+
config: { theme: { colors: { primary: "blue" } } },
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("extracts fields from different branches", () => {
|
|
63
|
+
const obj = {
|
|
64
|
+
meta: { name: "Button" },
|
|
65
|
+
usage: { when: ["action needed"] },
|
|
66
|
+
props: { variant: { type: "enum" } },
|
|
67
|
+
};
|
|
68
|
+
const result = projectFields(obj, ["meta.name", "usage.when"]);
|
|
69
|
+
expect(result).toEqual({
|
|
70
|
+
meta: { name: "Button" },
|
|
71
|
+
usage: { when: ["action needed"] },
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("mixed top-level and nested", () => {
|
|
77
|
+
it("handles mix of top-level and nested fields", () => {
|
|
78
|
+
const obj = {
|
|
79
|
+
name: "Button",
|
|
80
|
+
meta: { category: "actions" },
|
|
81
|
+
variants: [{ name: "Primary" }],
|
|
82
|
+
};
|
|
83
|
+
const result = projectFields(obj, ["name", "meta.category"]);
|
|
84
|
+
expect(result).toEqual({
|
|
85
|
+
name: "Button",
|
|
86
|
+
meta: { category: "actions" },
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("extracts entire nested object when parent is specified", () => {
|
|
91
|
+
const obj = {
|
|
92
|
+
meta: { name: "Button", category: "actions" },
|
|
93
|
+
usage: { when: ["x"], whenNot: ["y"] },
|
|
94
|
+
};
|
|
95
|
+
const result = projectFields(obj, ["meta"]);
|
|
96
|
+
expect(result).toEqual({
|
|
97
|
+
meta: { name: "Button", category: "actions" },
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("edge cases", () => {
|
|
103
|
+
it("handles null values in path (creates empty parent)", () => {
|
|
104
|
+
const obj = { meta: null };
|
|
105
|
+
const result = projectFields(obj as Record<string, unknown>, ["meta.name"]);
|
|
106
|
+
// When parent is null, we still create the path structure but value is undefined
|
|
107
|
+
expect(result).toEqual({ meta: {} });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("handles undefined values in path (creates empty parent)", () => {
|
|
111
|
+
const obj = { meta: undefined };
|
|
112
|
+
const result = projectFields(obj as Record<string, unknown>, ["meta.name"]);
|
|
113
|
+
// When parent is undefined, we still create the path structure but value is undefined
|
|
114
|
+
expect(result).toEqual({ meta: {} });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("handles arrays as values", () => {
|
|
118
|
+
const obj = {
|
|
119
|
+
usage: { when: ["use case 1", "use case 2"] },
|
|
120
|
+
};
|
|
121
|
+
const result = projectFields(obj, ["usage.when"]);
|
|
122
|
+
expect(result).toEqual({ usage: { when: ["use case 1", "use case 2"] } });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("handles empty object", () => {
|
|
126
|
+
const result = projectFields({}, ["name"]);
|
|
127
|
+
expect(result).toEqual({});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
package/src/mcp/bin.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startMcpServer } from './server.js';
|
|
3
|
+
|
|
4
|
+
// Parse command line arguments
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
let projectRoot = process.cwd();
|
|
7
|
+
let viewerUrl: string | undefined;
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < args.length; i++) {
|
|
10
|
+
const arg = args[i];
|
|
11
|
+
|
|
12
|
+
if (arg === '--project-root' || arg === '-p') {
|
|
13
|
+
projectRoot = args[++i] ?? projectRoot;
|
|
14
|
+
} else if (arg === '--viewer-url' || arg === '-u') {
|
|
15
|
+
viewerUrl = args[++i];
|
|
16
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
17
|
+
console.log(`
|
|
18
|
+
Usage: fragments-mcp [options]
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
-p, --project-root <path> Project root directory (default: cwd)
|
|
22
|
+
-u, --viewer-url <url> Viewer URL (default: http://localhost:6006)
|
|
23
|
+
-h, --help Show this help message
|
|
24
|
+
`);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Start server
|
|
30
|
+
startMcpServer({
|
|
31
|
+
projectRoot,
|
|
32
|
+
viewerUrl,
|
|
33
|
+
}).catch((error) => {
|
|
34
|
+
console.error('Failed to start MCP server:', error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
package/src/mcp/index.ts
ADDED