@fragments-sdk/core 0.1.0

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/src/index.ts ADDED
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Browser-safe exports for @fragments-sdk/cli/core
3
+ * For Node.js-only APIs (config, discovery, loader), import from '@fragments-sdk/cli' directly.
4
+ */
5
+
6
+ // Brand and default constants
7
+ export { BRAND, DEFAULTS } from "./constants.js";
8
+ export type { Brand, Defaults } from "./constants.js";
9
+
10
+ // Types
11
+ export type {
12
+ FragmentComponent,
13
+ FragmentMeta,
14
+ FragmentUsage,
15
+ PropType,
16
+ PropDefinition,
17
+ ControlType,
18
+ RelationshipType,
19
+ ComponentRelation,
20
+ VariantLoader,
21
+ PlayFunction,
22
+ PlayFunctionContext,
23
+ FragmentVariant,
24
+ FragmentDefinition,
25
+ FragmentsConfig,
26
+ SnippetPolicyConfig,
27
+ RegistryOptions,
28
+ CompiledFragment,
29
+ CompiledFragmentsFile,
30
+ BlockDefinition,
31
+ CompiledBlock,
32
+ RecipeDefinition, // @deprecated - use BlockDefinition
33
+ CompiledRecipe, // @deprecated - use CompiledBlock
34
+ // Contract and provenance types
35
+ FragmentContract,
36
+ FragmentGenerated,
37
+ // AI metadata type
38
+ AIMetadata,
39
+ // Screenshot types
40
+ ScreenshotConfig,
41
+ ServiceConfig,
42
+ Viewport,
43
+ Theme,
44
+ Screenshot,
45
+ ScreenshotMetadata,
46
+ DiffResult,
47
+ BoundingBox,
48
+ BaselineInfo,
49
+ Manifest,
50
+ VerifyRequest,
51
+ VerifyResult,
52
+ // Figma property mapping types
53
+ FigmaPropMapping,
54
+ FigmaStringMapping,
55
+ FigmaBooleanMapping,
56
+ FigmaEnumMapping,
57
+ FigmaInstanceMapping,
58
+ FigmaChildrenMapping,
59
+ FigmaTextContentMapping,
60
+ // Storybook filter configuration
61
+ StorybookFilterConfig,
62
+ // Token configuration
63
+ TokenConfig,
64
+ // Compiled token types
65
+ CompiledTokenEntry,
66
+ CompiledTokenData,
67
+ } from "./types.js";
68
+
69
+ // Performance presets
70
+ export {
71
+ resolvePerformanceConfig,
72
+ classifyComplexity,
73
+ formatBytes,
74
+ budgetBar,
75
+ PRESET_NAMES,
76
+ } from "./performance-presets.js";
77
+ export type {
78
+ PerformanceBudgets,
79
+ PerformanceConfig,
80
+ ComplexityTier,
81
+ PerformanceData as PerfData,
82
+ PerformanceSummary as PerfSummary,
83
+ } from "./performance-presets.js";
84
+
85
+ // Token types
86
+ export type {
87
+ TokenCategory,
88
+ DesignToken,
89
+ TokenRegistry,
90
+ TokenRegistryMeta,
91
+ EnhancedStyleDiffItem,
92
+ TokenFix,
93
+ TokenParseResult,
94
+ TokenParseError,
95
+ TokenMatchRequest,
96
+ TokenMatchResult,
97
+ TokenUsageSummary,
98
+ } from "./token-types.js";
99
+
100
+ // Schema validation (Zod is browser-safe)
101
+ export {
102
+ fragmentMetaSchema,
103
+ fragmentUsageSchema,
104
+ propDefinitionSchema,
105
+ componentRelationSchema,
106
+ fragmentVariantSchema,
107
+ fragmentDefinitionSchema,
108
+ fragmentsConfigSchema,
109
+ figmaPropMappingSchema,
110
+ // Contract and provenance schemas
111
+ fragmentContractSchema,
112
+ fragmentGeneratedSchema,
113
+ fragmentBanSchema,
114
+ blockDefinitionSchema,
115
+ recipeDefinitionSchema, // @deprecated - use blockDefinitionSchema
116
+ // AI metadata schema
117
+ aiMetadataSchema,
118
+ } from "./schema.js";
119
+
120
+ // Main API
121
+ export { defineFragment, compileFragment, defineBlock, compileBlock, defineRecipe, compileRecipe } from "./defineFragment.js";
122
+ export type { InferProps } from "./defineFragment.js";
123
+
124
+ // Story adapter (runtime conversion of Storybook modules)
125
+ export {
126
+ storyModuleToFragment,
127
+ setPreviewConfig,
128
+ getPreviewConfig,
129
+ // Re-export @storybook/csf utilities
130
+ toId,
131
+ storyNameFromExport,
132
+ isExportStory,
133
+ } from "./storyAdapter.js";
134
+ export type {
135
+ StoryModule,
136
+ StoryMeta,
137
+ Story,
138
+ StoryArgType,
139
+ StoryContext,
140
+ Decorator,
141
+ Loader,
142
+ PreviewConfig,
143
+ CSF2Story,
144
+ } from "./storyAdapter.js";
145
+
146
+ // Storybook adapter filtering
147
+ export {
148
+ checkStoryExclusion,
149
+ detectSubComponentPaths,
150
+ isForceIncluded,
151
+ isConfigExcluded,
152
+ } from "./storyFilters.js";
153
+ export type {
154
+ ExclusionReason,
155
+ ExclusionResult,
156
+ CheckStoryExclusionOpts,
157
+ } from "./storyFilters.js";
158
+
159
+ // Context generation for AI agents
160
+ export { generateContext } from "./context.js";
161
+ export type { ContextOptions, ContextResult } from "./context.js";
162
+
163
+ // Figma property mapping DSL
164
+ export { figma, isFigmaPropMapping, resolveFigmaMapping } from "./figma.js";
165
+
166
+ // Fragment JSON types (for .fragment.json files)
167
+ export type {
168
+ Fragment,
169
+ FragmentFigma,
170
+ FragmentUsage as FragmentJsonUsage,
171
+ FragmentDoNotItem,
172
+ FragmentPattern,
173
+ FragmentAccessibility,
174
+ FragmentRelated,
175
+ FragmentMeta as FragmentJsonMeta,
176
+ FragmentIndex,
177
+ FragmentRegistry,
178
+ RegistryComponentEntry,
179
+ RegistryPropEntry,
180
+ FragmentContextOptions,
181
+ } from "./fragment-types.js";
182
+
183
+ // Token parsing
184
+ export { parseTokenFile } from "./token-parser.js";
185
+ export type { ParsedToken, TokenParseOutput } from "./token-parser.js";
186
+
187
+ // Composition analysis
188
+ export { analyzeComposition } from "./composition.js";
189
+ export type {
190
+ CompositionAnalysis,
191
+ CompositionWarning,
192
+ CompositionSuggestion,
193
+ CompositionGuideline,
194
+ } from "./composition.js";
195
+
196
+ // Shared preview runtime
197
+ export {
198
+ executeVariantLoaders,
199
+ resolvePreviewRuntimeState,
200
+ usePreviewVariantRuntime,
201
+ PreviewVariantRuntime,
202
+ } from "./preview-runtime.js";
203
+ export type {
204
+ PreviewVariantLike,
205
+ PreviewRuntimeState,
206
+ PreviewRuntimeOptions,
207
+ } from "./preview-runtime.js";
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Performance budget presets and classification utilities.
3
+ *
4
+ * ESLint model: global defaults, zero-config, auto-measurement.
5
+ * Users configure 0-3 numbers. Per-component overrides are the `eslint-disable` equivalent.
6
+ */
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Types
10
+ // ---------------------------------------------------------------------------
11
+
12
+ export interface PerformanceBudgets {
13
+ /** Maximum gzipped bundle size in bytes */
14
+ bundleSize: number;
15
+ }
16
+
17
+ export interface PerformanceConfig {
18
+ preset: string;
19
+ budgets: PerformanceBudgets;
20
+ }
21
+
22
+ export type ComplexityTier = 'lightweight' | 'moderate' | 'heavy';
23
+
24
+ export interface PerformanceData {
25
+ /** Gzipped bundle size in bytes */
26
+ bundleSize: number;
27
+ /** Raw (minified, not gzipped) bundle size in bytes */
28
+ rawSize: number;
29
+ /** Complexity classification */
30
+ complexity: ComplexityTier;
31
+ /** Percentage of budget used (0-100+) */
32
+ budgetPercent: number;
33
+ /** Whether the component exceeds its budget */
34
+ overBudget: boolean;
35
+ /** ISO timestamp when measured */
36
+ measuredAt: string;
37
+ }
38
+
39
+ export interface PerformanceSummary {
40
+ /** Preset name used */
41
+ preset: string;
42
+ /** Budget applied in bytes */
43
+ budget: number;
44
+ /** Total components measured */
45
+ total: number;
46
+ /** Number of components over budget */
47
+ overBudget: number;
48
+ /** Distribution by tier */
49
+ tiers: Record<ComplexityTier, number>;
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Presets
54
+ // ---------------------------------------------------------------------------
55
+
56
+ const PRESETS: Record<string, PerformanceBudgets> = {
57
+ strict: { bundleSize: 8 * 1024 }, // 8KB gzipped
58
+ standard: { bundleSize: 15 * 1024 }, // 15KB gzipped
59
+ relaxed: { bundleSize: 30 * 1024 }, // 30KB gzipped
60
+ };
61
+
62
+ export const PRESET_NAMES = Object.keys(PRESETS) as readonly string[];
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Resolution
66
+ // ---------------------------------------------------------------------------
67
+
68
+ /**
69
+ * Resolve a performance config from user input.
70
+ * Accepts a preset name string or a custom config object.
71
+ */
72
+ export function resolvePerformanceConfig(
73
+ input: string | { preset?: string; budgets?: Partial<PerformanceBudgets> } | undefined
74
+ ): PerformanceConfig {
75
+ if (!input) {
76
+ return { preset: 'standard', budgets: PRESETS.standard };
77
+ }
78
+
79
+ if (typeof input === 'string') {
80
+ const budgets = PRESETS[input];
81
+ if (!budgets) {
82
+ throw new Error(
83
+ `Unknown performance preset "${input}". Available: ${PRESET_NAMES.join(', ')}`
84
+ );
85
+ }
86
+ return { preset: input, budgets };
87
+ }
88
+
89
+ const presetName = input.preset ?? 'standard';
90
+ const baseBudgets = PRESETS[presetName];
91
+ if (!baseBudgets) {
92
+ throw new Error(
93
+ `Unknown performance preset "${presetName}". Available: ${PRESET_NAMES.join(', ')}`
94
+ );
95
+ }
96
+
97
+ return {
98
+ preset: presetName,
99
+ budgets: {
100
+ bundleSize: input.budgets?.bundleSize ?? baseBudgets.bundleSize,
101
+ },
102
+ };
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Classification
107
+ // ---------------------------------------------------------------------------
108
+
109
+ /**
110
+ * Classify a component's complexity based on gzipped bundle size.
111
+ *
112
+ * - lightweight: < 5KB — simple, leaf components
113
+ * - moderate: < 15KB — typical composed components
114
+ * - heavy: >= 15KB — complex widgets with dependencies
115
+ */
116
+ export function classifyComplexity(gzipBytes: number): ComplexityTier {
117
+ if (gzipBytes < 5 * 1024) return 'lightweight';
118
+ if (gzipBytes < 15 * 1024) return 'moderate';
119
+ return 'heavy';
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Formatting helpers
124
+ // ---------------------------------------------------------------------------
125
+
126
+ /**
127
+ * Format bytes to a human-readable string (e.g. "2.1KB", "15.3KB").
128
+ */
129
+ export function formatBytes(bytes: number): string {
130
+ if (bytes < 1024) return `${bytes}B`;
131
+ const kb = bytes / 1024;
132
+ return kb < 10 ? `${kb.toFixed(1)}KB` : `${Math.round(kb)}KB`;
133
+ }
134
+
135
+ /**
136
+ * Create a visual budget bar for terminal output.
137
+ */
138
+ export function budgetBar(percent: number, width = 20): string {
139
+ const filled = Math.min(Math.round((percent / 100) * width), width);
140
+ const bar = '█'.repeat(filled) + '░'.repeat(width - filled);
141
+ return percent > 100 ? `\x1b[31m${bar}\x1b[0m` : `\x1b[32m${bar}\x1b[0m`;
142
+ }
@@ -0,0 +1,144 @@
1
+ import { useEffect, useState, type ReactNode } from "react";
2
+ import type { VariantLoader, VariantRenderOptions } from "./types.js";
3
+
4
+ /**
5
+ * Minimal contract for rendering a preview variant.
6
+ * Compatible with fragment variants and Storybook-adapted variants.
7
+ */
8
+ export interface PreviewVariantLike {
9
+ render: (options?: VariantRenderOptions) => ReactNode;
10
+ loaders?: VariantLoader[];
11
+ }
12
+
13
+ export interface PreviewRuntimeState {
14
+ content: ReactNode | null;
15
+ isLoading: boolean;
16
+ error: Error | null;
17
+ loadedData?: Record<string, unknown>;
18
+ }
19
+
20
+ export interface PreviewRuntimeOptions {
21
+ variant?: PreviewVariantLike | null;
22
+ loadedData?: Record<string, unknown>;
23
+ }
24
+
25
+ const EMPTY_STATE: PreviewRuntimeState = {
26
+ content: null,
27
+ isLoading: false,
28
+ error: null,
29
+ loadedData: undefined,
30
+ };
31
+
32
+ function toError(error: unknown): Error {
33
+ return error instanceof Error ? error : new Error(String(error));
34
+ }
35
+
36
+ /**
37
+ * Execute all variant loaders and merge their payloads.
38
+ * `loadedData` is applied last so host-level overrides win.
39
+ */
40
+ export async function executeVariantLoaders(
41
+ loaders: VariantLoader[] | undefined,
42
+ loadedData?: Record<string, unknown>,
43
+ ): Promise<Record<string, unknown> | undefined> {
44
+ const hasLoaders = !!loaders && loaders.length > 0;
45
+ if (!hasLoaders) {
46
+ return loadedData;
47
+ }
48
+
49
+ const results = await Promise.all(loaders.map((loader) => loader()));
50
+ const mergedFromLoaders = results.reduce<Record<string, unknown>>(
51
+ (acc, result) => ({ ...acc, ...result }),
52
+ {},
53
+ );
54
+
55
+ return loadedData ? { ...mergedFromLoaders, ...loadedData } : mergedFromLoaders;
56
+ }
57
+
58
+ /**
59
+ * Resolve a full runtime state (loader execution + render) in one async call.
60
+ * This is useful for testing and for hook/component orchestration.
61
+ */
62
+ export async function resolvePreviewRuntimeState(
63
+ options: PreviewRuntimeOptions,
64
+ ): Promise<PreviewRuntimeState> {
65
+ const { variant, loadedData } = options;
66
+ if (!variant) {
67
+ return EMPTY_STATE;
68
+ }
69
+
70
+ try {
71
+ const mergedLoadedData = await executeVariantLoaders(variant.loaders, loadedData);
72
+ const content = variant.render({ loadedData: mergedLoadedData });
73
+ return {
74
+ content,
75
+ isLoading: false,
76
+ error: null,
77
+ loadedData: mergedLoadedData,
78
+ };
79
+ } catch (error) {
80
+ return {
81
+ content: null,
82
+ isLoading: false,
83
+ error: toError(error),
84
+ loadedData: undefined,
85
+ };
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Hook for rendering a preview variant with loader support.
91
+ */
92
+ export function usePreviewVariantRuntime(
93
+ options: PreviewRuntimeOptions,
94
+ ): PreviewRuntimeState {
95
+ const { variant, loadedData } = options;
96
+ const [state, setState] = useState<PreviewRuntimeState>(EMPTY_STATE);
97
+
98
+ useEffect(() => {
99
+ let cancelled = false;
100
+
101
+ if (!variant) {
102
+ setState(EMPTY_STATE);
103
+ return () => {
104
+ cancelled = true;
105
+ };
106
+ }
107
+
108
+ const hasLoaders = !!variant.loaders && variant.loaders.length > 0;
109
+ setState({
110
+ content: null,
111
+ isLoading: hasLoaders,
112
+ error: null,
113
+ loadedData: undefined,
114
+ });
115
+
116
+ resolvePreviewRuntimeState({ variant, loadedData }).then((nextState) => {
117
+ if (!cancelled) {
118
+ setState(nextState);
119
+ }
120
+ });
121
+
122
+ return () => {
123
+ cancelled = true;
124
+ };
125
+ }, [variant, loadedData]);
126
+
127
+ return state;
128
+ }
129
+
130
+ interface PreviewVariantRuntimeProps extends PreviewRuntimeOptions {
131
+ children: (state: PreviewRuntimeState) => ReactNode;
132
+ }
133
+
134
+ /**
135
+ * Render-prop component wrapper around `usePreviewVariantRuntime`.
136
+ */
137
+ export function PreviewVariantRuntime({
138
+ variant,
139
+ loadedData,
140
+ children,
141
+ }: PreviewVariantRuntimeProps) {
142
+ const state = usePreviewVariantRuntime({ variant, loadedData });
143
+ return <>{children(state)}</>;
144
+ }