@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.
@@ -0,0 +1,141 @@
1
+ import type { FragmentDefinition, CompiledFragment, FragmentComponent, BlockDefinition, CompiledBlock } from './types.js';
2
+ import { fragmentDefinitionSchema, blockDefinitionSchema } from './schema.js';
3
+
4
+ /**
5
+ * Define a fragment for a component.
6
+ *
7
+ * This is the main API for creating fragment documentation.
8
+ * It provides runtime validation and type safety.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * import { defineFragment } from '@fragments-sdk/cli/core';
13
+ * import { Button } from './Button';
14
+ *
15
+ * export default defineFragment({
16
+ * component: Button,
17
+ * meta: {
18
+ * name: 'Button',
19
+ * description: 'Primary action trigger',
20
+ * category: 'actions',
21
+ * },
22
+ * usage: {
23
+ * when: ['User needs to trigger an action'],
24
+ * whenNot: ['Navigation without side effects'],
25
+ * },
26
+ * props: {
27
+ * variant: {
28
+ * type: 'enum',
29
+ * values: ['primary', 'secondary'],
30
+ * default: 'primary',
31
+ * description: 'Visual style',
32
+ * },
33
+ * },
34
+ * variants: [
35
+ * {
36
+ * name: 'Default',
37
+ * description: 'Default button',
38
+ * render: () => <Button>Click me</Button>,
39
+ * },
40
+ * ],
41
+ * });
42
+ * ```
43
+ */
44
+ export function defineFragment<TProps>(
45
+ definition: FragmentDefinition<TProps>
46
+ ): FragmentDefinition<TProps> {
47
+ // Validate at runtime in development
48
+ if (process.env.NODE_ENV !== 'production') {
49
+ const result = fragmentDefinitionSchema.safeParse(definition);
50
+ if (!result.success) {
51
+ const errors = result.error.errors
52
+ .map((e) => ` - ${e.path.join('.')}: ${e.message}`)
53
+ .join('\n');
54
+ throw new Error(
55
+ `Invalid fragment definition for "${definition.meta?.name || 'unknown'}":\n${errors}`
56
+ );
57
+ }
58
+ }
59
+
60
+ return definition;
61
+ }
62
+
63
+ /**
64
+ * Compile a fragment definition to JSON-serializable format.
65
+ * Used for generating fragments.json for AI consumption.
66
+ */
67
+ export function compileFragment(
68
+ definition: FragmentDefinition,
69
+ filePath: string
70
+ ): CompiledFragment {
71
+ return {
72
+ filePath,
73
+ meta: definition.meta,
74
+ usage: definition.usage,
75
+ props: definition.props,
76
+ relations: definition.relations,
77
+ variants: definition.variants.map((v) => ({
78
+ name: v.name,
79
+ description: v.description,
80
+ code: v.code,
81
+ figma: v.figma,
82
+ })),
83
+ contract: definition.contract,
84
+ _generated: definition._generated,
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Define a composition block.
90
+ *
91
+ * Blocks are pure data describing how design system components
92
+ * wire together for common use cases.
93
+ */
94
+ export function defineBlock(definition: BlockDefinition): BlockDefinition {
95
+ if (process.env.NODE_ENV !== 'production') {
96
+ const result = blockDefinitionSchema.safeParse(definition);
97
+ if (!result.success) {
98
+ const errors = result.error.errors
99
+ .map((e) => ` - ${e.path.join('.')}: ${e.message}`)
100
+ .join('\n');
101
+ throw new Error(
102
+ `Invalid block definition for "${definition.name || 'unknown'}":\n${errors}`
103
+ );
104
+ }
105
+ }
106
+
107
+ return definition;
108
+ }
109
+
110
+ /**
111
+ * @deprecated Use defineBlock instead
112
+ */
113
+ export const defineRecipe = defineBlock;
114
+
115
+ /**
116
+ * Compile a block definition to JSON-serializable format.
117
+ */
118
+ export function compileBlock(
119
+ definition: BlockDefinition,
120
+ filePath: string
121
+ ): CompiledBlock {
122
+ return {
123
+ filePath,
124
+ name: definition.name,
125
+ description: definition.description,
126
+ category: definition.category,
127
+ components: definition.components,
128
+ code: definition.code,
129
+ tags: definition.tags,
130
+ };
131
+ }
132
+
133
+ /**
134
+ * @deprecated Use compileBlock instead
135
+ */
136
+ export const compileRecipe = compileBlock;
137
+
138
+ /**
139
+ * Type helper for extracting props type from a component
140
+ */
141
+ export type InferProps<T> = T extends FragmentComponent<infer P> ? P : never;
package/src/figma.ts ADDED
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Figma property mapping DSL
3
+ *
4
+ * Provides helpers for mapping Figma component properties to code props.
5
+ * Inspired by Figma Code Connect's API.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { defineFragment, figma } from '@fragments-sdk/cli/core';
10
+ *
11
+ * export default defineFragment({
12
+ * component: Button,
13
+ * meta: {
14
+ * name: 'Button',
15
+ * description: 'Primary action trigger',
16
+ * category: 'actions',
17
+ * figma: 'https://figma.com/file/abc/Design?node-id=1-2',
18
+ * figmaProps: {
19
+ * children: figma.string('Label'),
20
+ * disabled: figma.boolean('Disabled'),
21
+ * variant: figma.enum('Type', {
22
+ * 'Primary': 'primary',
23
+ * 'Secondary': 'secondary',
24
+ * }),
25
+ * },
26
+ * },
27
+ * // ...
28
+ * });
29
+ * ```
30
+ */
31
+
32
+ import type {
33
+ FigmaStringMapping,
34
+ FigmaBooleanMapping,
35
+ FigmaEnumMapping,
36
+ FigmaInstanceMapping,
37
+ FigmaChildrenMapping,
38
+ FigmaTextContentMapping,
39
+ } from './types.js';
40
+
41
+ /**
42
+ * Map a Figma text property to a string prop.
43
+ *
44
+ * @param figmaProperty - The name of the text property in Figma
45
+ * @returns A string mapping descriptor
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * figmaProps: {
50
+ * label: figma.string('Button Text'),
51
+ * placeholder: figma.string('Placeholder'),
52
+ * }
53
+ * ```
54
+ */
55
+ function string(figmaProperty: string): FigmaStringMapping {
56
+ return {
57
+ __type: 'figma-string',
58
+ figmaProperty,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Map a Figma boolean property to a boolean prop.
64
+ * Optionally map true/false to different values.
65
+ *
66
+ * @param figmaProperty - The name of the boolean property in Figma
67
+ * @param valueMapping - Optional mapping of true/false to other values
68
+ * @returns A boolean mapping descriptor
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * figmaProps: {
73
+ * disabled: figma.boolean('Disabled'),
74
+ * // Map boolean to string values
75
+ * size: figma.boolean('Large', { true: 'lg', false: 'md' }),
76
+ * }
77
+ * ```
78
+ */
79
+ function boolean(
80
+ figmaProperty: string,
81
+ valueMapping?: { true: unknown; false: unknown }
82
+ ): FigmaBooleanMapping {
83
+ return {
84
+ __type: 'figma-boolean',
85
+ figmaProperty,
86
+ valueMapping,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Map a Figma variant property to an enum prop.
92
+ *
93
+ * @param figmaProperty - The name of the variant property in Figma
94
+ * @param valueMapping - Mapping of Figma values to code values
95
+ * @returns An enum mapping descriptor
96
+ *
97
+ * @example
98
+ * ```tsx
99
+ * figmaProps: {
100
+ * variant: figma.enum('Type', {
101
+ * 'Primary': 'primary',
102
+ * 'Secondary': 'secondary',
103
+ * 'Outline': 'outline',
104
+ * }),
105
+ * size: figma.enum('Size', {
106
+ * 'Small': 'sm',
107
+ * 'Medium': 'md',
108
+ * 'Large': 'lg',
109
+ * }),
110
+ * }
111
+ * ```
112
+ */
113
+ function enumValue<T extends Record<string, unknown>>(
114
+ figmaProperty: string,
115
+ valueMapping: T
116
+ ): FigmaEnumMapping {
117
+ return {
118
+ __type: 'figma-enum',
119
+ figmaProperty,
120
+ valueMapping,
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Reference a nested Figma component instance.
126
+ * Use this when a prop accepts a component that's represented
127
+ * as an instance swap in Figma.
128
+ *
129
+ * @param figmaProperty - The name of the instance property in Figma
130
+ * @returns An instance mapping descriptor
131
+ *
132
+ * @example
133
+ * ```tsx
134
+ * figmaProps: {
135
+ * icon: figma.instance('Icon'),
136
+ * avatar: figma.instance('Avatar'),
137
+ * }
138
+ * ```
139
+ */
140
+ function instance(figmaProperty: string): FigmaInstanceMapping {
141
+ return {
142
+ __type: 'figma-instance',
143
+ figmaProperty,
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Render children from specific Figma layers.
149
+ * Use this when children are represented as named layers in Figma.
150
+ *
151
+ * @param layers - Array of layer names to include as children
152
+ * @returns A children mapping descriptor
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * figmaProps: {
157
+ * children: figma.children(['Title', 'Description', 'Actions']),
158
+ * }
159
+ * ```
160
+ */
161
+ function children(layers: string[]): FigmaChildrenMapping {
162
+ return {
163
+ __type: 'figma-children',
164
+ layers,
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Extract text content from a Figma text layer.
170
+ * Use this when a prop should be the actual text from a layer.
171
+ *
172
+ * @param layer - The name of the text layer in Figma
173
+ * @returns A text content mapping descriptor
174
+ *
175
+ * @example
176
+ * ```tsx
177
+ * figmaProps: {
178
+ * title: figma.textContent('Header Text'),
179
+ * description: figma.textContent('Body Text'),
180
+ * }
181
+ * ```
182
+ */
183
+ function textContent(layer: string): FigmaTextContentMapping {
184
+ return {
185
+ __type: 'figma-text-content',
186
+ layer,
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Figma property mapping helpers.
192
+ *
193
+ * Use these to define how Figma properties map to your component props.
194
+ * The mappings are used for:
195
+ * - Generating accurate code snippets in Figma Dev Mode
196
+ * - AI agents understanding the design-to-code relationship
197
+ * - Automated design verification
198
+ */
199
+ export const figma = {
200
+ string,
201
+ boolean,
202
+ enum: enumValue,
203
+ instance,
204
+ children,
205
+ textContent,
206
+ } as const;
207
+
208
+ /**
209
+ * Helper type to check if a value is a Figma prop mapping
210
+ */
211
+ export function isFigmaPropMapping(
212
+ value: unknown
213
+ ): value is FigmaStringMapping | FigmaBooleanMapping | FigmaEnumMapping | FigmaInstanceMapping | FigmaChildrenMapping | FigmaTextContentMapping {
214
+ if (typeof value !== 'object' || value === null || !('__type' in value)) {
215
+ return false;
216
+ }
217
+ const typeValue = (value as Record<string, unknown>).__type;
218
+ return typeof typeValue === 'string' && typeValue.startsWith('figma-');
219
+ }
220
+
221
+ /**
222
+ * Resolve a Figma prop mapping to an actual value given Figma property values.
223
+ *
224
+ * @param mapping - The Figma prop mapping
225
+ * @param figmaValues - Object containing Figma property values
226
+ * @returns The resolved value for the code prop
227
+ */
228
+ export function resolveFigmaMapping(
229
+ mapping: FigmaStringMapping | FigmaBooleanMapping | FigmaEnumMapping | FigmaInstanceMapping | FigmaChildrenMapping | FigmaTextContentMapping,
230
+ figmaValues: Record<string, unknown>
231
+ ): unknown {
232
+ switch (mapping.__type) {
233
+ case 'figma-string':
234
+ return figmaValues[mapping.figmaProperty] ?? '';
235
+
236
+ case 'figma-boolean': {
237
+ const boolValue = figmaValues[mapping.figmaProperty] as boolean;
238
+ if (mapping.valueMapping) {
239
+ return boolValue ? mapping.valueMapping.true : mapping.valueMapping.false;
240
+ }
241
+ return boolValue;
242
+ }
243
+
244
+ case 'figma-enum': {
245
+ const enumKey = figmaValues[mapping.figmaProperty] as string;
246
+ return mapping.valueMapping[enumKey] ?? enumKey;
247
+ }
248
+
249
+ case 'figma-instance':
250
+ // Instance mappings return the instance reference
251
+ return figmaValues[mapping.figmaProperty];
252
+
253
+ case 'figma-children':
254
+ // Children mappings return array of layer contents
255
+ return mapping.layers.map((layer) => figmaValues[layer]);
256
+
257
+ case 'figma-text-content':
258
+ return figmaValues[mapping.layer] ?? '';
259
+
260
+ default:
261
+ return undefined;
262
+ }
263
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * TypeScript types for Fragment JSON files.
3
+ * These types correspond to the JSON schemas in ./schema/
4
+ */
5
+
6
+ /**
7
+ * Figma design links and mappings
8
+ */
9
+ export interface FragmentFigma {
10
+ /** Figma file URL */
11
+ file?: string;
12
+ /** Default Figma node ID for this component */
13
+ nodeId?: string;
14
+ /** Mapping of variant names to Figma node IDs */
15
+ variants?: Record<string, string>;
16
+ }
17
+
18
+ /**
19
+ * Anti-pattern with optional alternative
20
+ */
21
+ export interface FragmentDoNotItem {
22
+ /** What not to do */
23
+ text: string;
24
+ /** Component name to use instead */
25
+ instead?: string;
26
+ }
27
+
28
+ /**
29
+ * Usage pattern with code example
30
+ */
31
+ export interface FragmentPattern {
32
+ /** Pattern name */
33
+ name: string;
34
+ /** Code example */
35
+ code: string;
36
+ /** When to use this pattern */
37
+ description?: string;
38
+ }
39
+
40
+ /**
41
+ * Usage guidelines for AI agents and developers
42
+ */
43
+ export interface FragmentUsage {
44
+ /** Scenarios when this component should be used */
45
+ when?: string[];
46
+ /** Anti-patterns and what to use instead */
47
+ doNot?: (string | FragmentDoNotItem)[];
48
+ /** Common usage patterns with code examples */
49
+ patterns?: FragmentPattern[];
50
+ }
51
+
52
+ /**
53
+ * Accessibility requirements and guidelines
54
+ */
55
+ export interface FragmentAccessibility {
56
+ /** ARIA role this component implements */
57
+ role?: string;
58
+ /** Accessibility requirements */
59
+ requirements?: string[];
60
+ /** Keyboard interaction patterns (key -> description) */
61
+ keyboard?: Record<string, string>;
62
+ }
63
+
64
+ /**
65
+ * Relationships to other components
66
+ */
67
+ export interface FragmentRelated {
68
+ /** Similar components that might be alternatives */
69
+ similar?: string[];
70
+ /** Components commonly used together with this one */
71
+ composedWith?: string[];
72
+ /** Parent components or patterns where this is commonly used */
73
+ usedIn?: string[];
74
+ }
75
+
76
+ /**
77
+ * Administrative metadata
78
+ */
79
+ export interface FragmentMeta {
80
+ /** Team or person responsible for this component */
81
+ owner?: string;
82
+ /** Component lifecycle status */
83
+ status?: "draft" | "experimental" | "beta" | "stable" | "deprecated";
84
+ /** Version when this component was introduced */
85
+ since?: string;
86
+ /** Version when this component was deprecated */
87
+ deprecatedSince?: string;
88
+ /** Why this component was deprecated and what to use instead */
89
+ deprecatedReason?: string;
90
+ /** Tags for categorization and search */
91
+ tags?: string[];
92
+ }
93
+
94
+ /**
95
+ * Fragment JSON file structure (.fragment.json)
96
+ * Contains enrichment metadata for a component
97
+ */
98
+ export interface Fragment {
99
+ /** JSON Schema reference */
100
+ $schema?: string;
101
+ /** Component name (must match the component export name) */
102
+ name: string;
103
+ /** Brief description of the component's purpose */
104
+ description?: string;
105
+ /** Figma design links and mappings */
106
+ figma?: FragmentFigma;
107
+ /** Usage guidelines for AI agents and developers */
108
+ usage?: FragmentUsage;
109
+ /** Accessibility requirements and guidelines */
110
+ accessibility?: FragmentAccessibility;
111
+ /** Relationships to other components */
112
+ related?: FragmentRelated;
113
+ /** Administrative metadata */
114
+ meta?: FragmentMeta;
115
+ }
116
+
117
+ /**
118
+ * Prop entry in the registry
119
+ */
120
+ export interface RegistryPropEntry {
121
+ /** TypeScript type (e.g., 'string', 'boolean', enum values) */
122
+ type?: string;
123
+ /** Simplified type category */
124
+ typeKind?: "string" | "number" | "boolean" | "enum" | "object" | "array" | "function" | "node" | "element" | "union" | "unknown";
125
+ /** For enum types, the allowed values */
126
+ options?: string[];
127
+ /** Default value if specified */
128
+ default?: unknown;
129
+ /** Whether this prop is required */
130
+ required?: boolean;
131
+ /** Prop description from JSDoc or TypeScript */
132
+ description?: string;
133
+ }
134
+
135
+ /**
136
+ * Component entry in the registry (simplified - focuses on paths and enrichment)
137
+ */
138
+ export interface RegistryComponentEntry {
139
+ /** Relative path to the component source file */
140
+ path: string;
141
+ /** Relative path to the .fragment.json file (if exists) */
142
+ fragmentPath?: string;
143
+ /** Relative path to the .stories.tsx file (if exists) */
144
+ storyPath?: string;
145
+ /** Component category (inferred from directory or fragment) */
146
+ category?: string;
147
+ /** Component lifecycle status (from fragment) */
148
+ status?: "draft" | "experimental" | "beta" | "stable" | "deprecated";
149
+ /** Component description (from fragment or JSDoc) */
150
+ description?: string;
151
+ /** Has human-authored enrichment (fragment file exists with content beyond skeleton) */
152
+ hasEnrichment?: boolean;
153
+ /** Extracted prop definitions - only included if config.registry.includeProps is true */
154
+ props?: Record<string, RegistryPropEntry>;
155
+ /** Named exports from the component file */
156
+ exports?: string[];
157
+ /** Component dependencies (other components used) */
158
+ dependencies?: string[];
159
+ /** Merged fragment enrichment data - only included if config.registry.embedFragments is true */
160
+ fragment?: Fragment;
161
+ }
162
+
163
+ /**
164
+ * Minimal component index (.fragments/index.json)
165
+ * Ultra-light name → path mapping for quick lookups
166
+ */
167
+ export interface FragmentIndex {
168
+ /** Schema version */
169
+ version: string;
170
+ /** When this index was generated */
171
+ generatedAt: string;
172
+ /** Simple name → path mapping */
173
+ components: Record<string, string>;
174
+ /** Categories for grouping */
175
+ categories?: Record<string, string[]>;
176
+ }
177
+
178
+ /**
179
+ * Registry file structure (.fragments/registry.json)
180
+ * Component index with resolved paths and optional metadata
181
+ */
182
+ export interface FragmentRegistry {
183
+ /** JSON Schema reference */
184
+ $schema?: string;
185
+ /** Schema version */
186
+ version: string;
187
+ /** When this registry was generated */
188
+ generatedAt: string;
189
+ /** Component count for quick reference */
190
+ componentCount: number;
191
+ /** Component index keyed by component name */
192
+ components: Record<string, RegistryComponentEntry>;
193
+ /** Components grouped by category */
194
+ categories?: Record<string, string[]>;
195
+ }
196
+
197
+ /**
198
+ * Context file options for generation
199
+ */
200
+ export interface FragmentContextOptions {
201
+ /** Output format */
202
+ format?: "markdown" | "json";
203
+ /** Compact mode - minimal output for token efficiency */
204
+ compact?: boolean;
205
+ /** What to include in the output */
206
+ include?: {
207
+ /** Include prop details (default: true) */
208
+ props?: boolean;
209
+ /** Include code examples (default: false) */
210
+ code?: boolean;
211
+ /** Include related components (default: true) */
212
+ relations?: boolean;
213
+ };
214
+ }