@fragments-sdk/cli 0.4.4 → 0.5.1

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.
Files changed (58) hide show
  1. package/README.md +1 -1
  2. package/dist/bin.js +12 -12
  3. package/dist/{chunk-NOTYONHY.js → chunk-2DJH4F4P.js} +2 -2
  4. package/dist/{chunk-5CKYLCJH.js → chunk-2H2JAA3U.js} +35 -7
  5. package/dist/chunk-2H2JAA3U.js.map +1 -0
  6. package/dist/{chunk-AW7MWOUH.js → chunk-ICAIQ57V.js} +9 -5
  7. package/dist/chunk-ICAIQ57V.js.map +1 -0
  8. package/dist/{chunk-5ZYEOHYK.js → chunk-IOJE35DZ.js} +2 -2
  9. package/dist/{chunk-ZFKGX3QK.js → chunk-U4GQ2JTD.js} +47 -14
  10. package/dist/chunk-U4GQ2JTD.js.map +1 -0
  11. package/dist/{chunk-G3M3MPQ6.js → chunk-V7YLRR4C.js} +135 -244
  12. package/dist/chunk-V7YLRR4C.js.map +1 -0
  13. package/dist/{chunk-J4SI5RIH.js → chunk-XNWDI6UT.js} +4 -4
  14. package/dist/{core-LNXDLXDP.js → core-DKHB7FYV.js} +11 -3
  15. package/dist/{generate-OIXXHOWR.js → generate-KL24VZVD.js} +4 -4
  16. package/dist/index.d.ts +2 -259
  17. package/dist/index.js +6 -6
  18. package/dist/{init-EVPXIDW4.js → init-NZB55B5O.js} +4 -4
  19. package/dist/mcp-bin.js +290 -37
  20. package/dist/mcp-bin.js.map +1 -1
  21. package/dist/scan-ESEXV7LF.js +12 -0
  22. package/dist/{service-K52ORLCJ.js → service-RWUMZ3EW.js} +4 -4
  23. package/dist/{static-viewer-JNQIHA4B.js → static-viewer-O37MJ5B6.js} +4 -4
  24. package/dist/{test-USARUEFW.js → test-ECPEXFDN.js} +3 -3
  25. package/dist/{tokens-C6YHBOQE.js → tokens-ITADYVPF.js} +5 -5
  26. package/dist/{viewer-H7TVFT4E.js → viewer-YDGFDTK5.js} +13 -13
  27. package/package.json +3 -2
  28. package/src/build.ts +53 -13
  29. package/src/core/constants.ts +4 -1
  30. package/src/core/context.ts +2 -380
  31. package/src/core/defineSegment.ts +21 -11
  32. package/src/core/discovery.ts +52 -4
  33. package/src/core/index.ts +14 -4
  34. package/src/core/loader.ts +3 -0
  35. package/src/core/node.ts +3 -1
  36. package/src/core/parser.ts +1 -1
  37. package/src/core/schema.ts +7 -2
  38. package/src/core/token-parser.ts +211 -0
  39. package/src/core/types.ts +16 -66
  40. package/src/mcp/__tests__/findFragmentsJson.test.ts +30 -0
  41. package/src/mcp/server.ts +361 -40
  42. package/dist/chunk-5CKYLCJH.js.map +0 -1
  43. package/dist/chunk-AW7MWOUH.js.map +0 -1
  44. package/dist/chunk-G3M3MPQ6.js.map +0 -1
  45. package/dist/chunk-ZFKGX3QK.js.map +0 -1
  46. package/dist/scan-YVYD64GD.js +0 -12
  47. /package/dist/{chunk-NOTYONHY.js.map → chunk-2DJH4F4P.js.map} +0 -0
  48. /package/dist/{chunk-5ZYEOHYK.js.map → chunk-IOJE35DZ.js.map} +0 -0
  49. /package/dist/{chunk-J4SI5RIH.js.map → chunk-XNWDI6UT.js.map} +0 -0
  50. /package/dist/{core-LNXDLXDP.js.map → core-DKHB7FYV.js.map} +0 -0
  51. /package/dist/{generate-OIXXHOWR.js.map → generate-KL24VZVD.js.map} +0 -0
  52. /package/dist/{init-EVPXIDW4.js.map → init-NZB55B5O.js.map} +0 -0
  53. /package/dist/{scan-YVYD64GD.js.map → scan-ESEXV7LF.js.map} +0 -0
  54. /package/dist/{service-K52ORLCJ.js.map → service-RWUMZ3EW.js.map} +0 -0
  55. /package/dist/{static-viewer-JNQIHA4B.js.map → static-viewer-O37MJ5B6.js.map} +0 -0
  56. /package/dist/{test-USARUEFW.js.map → test-ECPEXFDN.js.map} +0 -0
  57. /package/dist/{tokens-C6YHBOQE.js.map → tokens-ITADYVPF.js.map} +0 -0
  58. /package/dist/{viewer-H7TVFT4E.js.map → viewer-YDGFDTK5.js.map} +0 -0
@@ -6,13 +6,13 @@ import {
6
6
  findStorybookDir,
7
7
  generatePreviewModule,
8
8
  loadConfig
9
- } from "./chunk-5CKYLCJH.js";
9
+ } from "./chunk-2H2JAA3U.js";
10
10
  import {
11
11
  generateContext
12
- } from "./chunk-G3M3MPQ6.js";
12
+ } from "./chunk-V7YLRR4C.js";
13
13
  import {
14
14
  BRAND
15
- } from "./chunk-AW7MWOUH.js";
15
+ } from "./chunk-ICAIQ57V.js";
16
16
 
17
17
  // src/viewer/server.ts
18
18
  import {
@@ -242,7 +242,7 @@ var sharedRenderPool = null;
242
242
  var browserPoolModule = null;
243
243
  async function getSharedRenderPool() {
244
244
  if (!browserPoolModule) {
245
- browserPoolModule = await import("./service-K52ORLCJ.js");
245
+ browserPoolModule = await import("./service-RWUMZ3EW.js");
246
246
  }
247
247
  if (!sharedRenderPool) {
248
248
  sharedRenderPool = new browserPoolModule.BrowserPool({
@@ -471,7 +471,7 @@ function segmentsPlugin(options) {
471
471
  const address = _server.httpServer?.address();
472
472
  const port = typeof address === "object" && address ? address.port : 6006;
473
473
  const renderViewport = viewport || { width: 800, height: 600 };
474
- const { FigmaClient, bufferToBase64Url } = await import("./service-K52ORLCJ.js");
474
+ const { FigmaClient, bufferToBase64Url } = await import("./service-RWUMZ3EW.js");
475
475
  const figmaClient = new FigmaClient({
476
476
  accessToken: figmaToken
477
477
  });
@@ -562,7 +562,7 @@ function segmentsPlugin(options) {
562
562
  );
563
563
  return;
564
564
  }
565
- const { FigmaClient } = await import("./service-K52ORLCJ.js");
565
+ const { FigmaClient } = await import("./service-RWUMZ3EW.js");
566
566
  const figmaClient = new FigmaClient({ accessToken: figmaToken });
567
567
  const { fileKey, nodeId } = figmaClient.parseUrl(figmaUrl);
568
568
  const figmaDesignProps = await figmaClient.getNodeProperties(
@@ -603,7 +603,7 @@ function segmentsPlugin(options) {
603
603
  }));
604
604
  return;
605
605
  }
606
- const { getSharedTokenRegistry } = await import("./service-K52ORLCJ.js");
606
+ const { getSharedTokenRegistry } = await import("./service-RWUMZ3EW.js");
607
607
  const registry = getSharedTokenRegistry();
608
608
  if (!registry.isInitialized()) {
609
609
  await registry.initialize(config.tokens, projectRoot);
@@ -663,7 +663,7 @@ function segmentsPlugin(options) {
663
663
  }));
664
664
  return;
665
665
  }
666
- const { getSharedTokenRegistry } = await import("./service-K52ORLCJ.js");
666
+ const { getSharedTokenRegistry } = await import("./service-RWUMZ3EW.js");
667
667
  const registry = getSharedTokenRegistry();
668
668
  if (!registry.isInitialized()) {
669
669
  await registry.initialize(config.tokens, projectRoot);
@@ -725,7 +725,7 @@ function segmentsPlugin(options) {
725
725
  res.end(JSON.stringify({ error: "Could not resolve segment file path" }));
726
726
  return;
727
727
  }
728
- const { getSharedTokenRegistry } = await import("./service-K52ORLCJ.js");
728
+ const { getSharedTokenRegistry } = await import("./service-RWUMZ3EW.js");
729
729
  const registry = getSharedTokenRegistry();
730
730
  if (!registry.isInitialized()) {
731
731
  await registry.initialize(config.tokens, projectRoot);
@@ -851,7 +851,7 @@ function segmentsPlugin(options) {
851
851
  }
852
852
  const { writeFile, mkdir } = await import("fs/promises");
853
853
  const { join: join2 } = await import("path");
854
- const { BRAND: BRAND2 } = await import("./core-LNXDLXDP.js");
854
+ const { BRAND: BRAND2 } = await import("./core-DKHB7FYV.js");
855
855
  const fragmentsDir = join2(projectRoot, BRAND2.dataDir, BRAND2.componentsDir);
856
856
  await mkdir(fragmentsDir, { recursive: true });
857
857
  const fragmentPath = join2(
@@ -906,7 +906,7 @@ function segmentsPlugin(options) {
906
906
  const {
907
907
  getSharedTokenRegistry,
908
908
  generateTokenPatches
909
- } = await import("./service-K52ORLCJ.js");
909
+ } = await import("./service-RWUMZ3EW.js");
910
910
  const registry = getSharedTokenRegistry();
911
911
  if (!registry.isInitialized()) {
912
912
  await registry.initialize(config.tokens, projectRoot);
@@ -1568,7 +1568,7 @@ async function loadFullSegmentForCompare(_server, _segmentFiles, componentName,
1568
1568
  }
1569
1569
  }
1570
1570
  async function compareImages(image1Base64, image2Base64, threshold) {
1571
- const { DiffEngine, base64UrlToBuffer, bufferToBase64Url } = await import("./service-K52ORLCJ.js");
1571
+ const { DiffEngine, base64UrlToBuffer, bufferToBase64Url } = await import("./service-RWUMZ3EW.js");
1572
1572
  const { PNG } = await import("pngjs");
1573
1573
  const buffer1 = base64UrlToBuffer(image1Base64);
1574
1574
  const buffer2 = base64UrlToBuffer(image2Base64);
@@ -11101,4 +11101,4 @@ export {
11101
11101
  segmentsPlugin,
11102
11102
  useTheme
11103
11103
  };
11104
- //# sourceMappingURL=viewer-H7TVFT4E.js.map
11104
+ //# sourceMappingURL=viewer-YDGFDTK5.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragments-sdk/cli",
3
- "version": "0.4.4",
3
+ "version": "0.5.1",
4
4
  "description": "CLI, MCP server, and dev tools for Fragments design system",
5
5
  "type": "module",
6
6
  "bin": {
@@ -63,7 +63,8 @@
63
63
  "tailwindcss": "^3.4.17",
64
64
  "vite": "^6.0.0",
65
65
  "vite-plugin-svgr": "^4.5.0",
66
- "zod": "^3.24.1"
66
+ "zod": "^3.24.1",
67
+ "@fragments-sdk/context": "0.2.0"
67
68
  },
68
69
  "devDependencies": {
69
70
  "@types/babel__generator": "^7.6.8",
package/src/build.ts CHANGED
@@ -5,13 +5,15 @@ import type {
5
5
  SegmentsConfig,
6
6
  CompiledSegmentsFile,
7
7
  CompiledSegment,
8
- CompiledRecipe,
8
+ CompiledBlock,
9
+ CompiledTokenData,
9
10
  } from "./core/index.js";
10
- import { BRAND, compileRecipe } from "./core/index.js";
11
- import type { RecipeDefinition } from "./core/index.js";
11
+ import { BRAND, compileBlock, parseTokenFile } from "./core/index.js";
12
+ import type { BlockDefinition } from "./core/index.js";
12
13
  import {
13
14
  discoverSegmentFiles,
14
- discoverRecipeFiles,
15
+ discoverBlockFiles,
16
+ discoverTokenFiles,
15
17
  parseSegmentFile,
16
18
  loadSegmentFile,
17
19
  generateRegistry,
@@ -124,11 +126,11 @@ export async function buildSegments(
124
126
  }
125
127
  }
126
128
 
127
- // Discover and compile recipe files
128
- const recipes: Record<string, CompiledRecipe> = {};
129
+ // Discover and compile block files
130
+ const blocks: Record<string, CompiledBlock> = {};
129
131
  try {
130
- const recipeFiles = await discoverRecipeFiles(configDir, config.exclude);
131
- for (const file of recipeFiles) {
132
+ const blockFiles = await discoverBlockFiles(configDir, config.exclude);
133
+ for (const file of blockFiles) {
132
134
  try {
133
135
  // loadSegmentFile uses esbuild to bundle+evaluate, returns default export
134
136
  // CJS/ESM interop may double-wrap the default export
@@ -139,18 +141,55 @@ export async function buildSegments(
139
141
  }
140
142
  const def = raw;
141
143
  if (def && typeof def === 'object' && 'name' in def && 'code' in def && 'components' in def) {
142
- const compiled = compileRecipe(def as unknown as RecipeDefinition, file.relativePath);
143
- recipes[compiled.name] = compiled;
144
+ const compiled = compileBlock(def as unknown as BlockDefinition, file.relativePath);
145
+ blocks[compiled.name] = compiled;
144
146
  }
145
147
  } catch (error) {
146
148
  warnings.push({
147
149
  file: file.relativePath,
148
- warning: `Failed to load recipe: ${error instanceof Error ? error.message : String(error)}`,
150
+ warning: `Failed to load block: ${error instanceof Error ? error.message : String(error)}`,
149
151
  });
150
152
  }
151
153
  }
152
154
  } catch {
153
- // Recipe discovery failure is non-fatal
155
+ // Block discovery failure is non-fatal
156
+ }
157
+
158
+ // Discover and extract design tokens from SCSS/CSS files
159
+ let tokens: CompiledTokenData | undefined;
160
+ try {
161
+ const tokenPatterns = config.tokens?.include;
162
+ const tokenFiles = await discoverTokenFiles(configDir, tokenPatterns, config.exclude);
163
+ if (tokenFiles.length > 0) {
164
+ // Merge tokens from all discovered files
165
+ const mergedCategories: Record<string, Array<{ name: string; description?: string }>> = {};
166
+ let prefix = '--';
167
+ let total = 0;
168
+
169
+ for (const file of tokenFiles) {
170
+ const content = await readFile(file.absolutePath, 'utf-8');
171
+ const parsed = parseTokenFile(content, file.relativePath);
172
+ prefix = parsed.prefix; // Use last file's prefix (usually consistent)
173
+ total += parsed.total;
174
+ for (const [cat, catTokens] of Object.entries(parsed.categories)) {
175
+ if (!mergedCategories[cat]) {
176
+ mergedCategories[cat] = [];
177
+ }
178
+ for (const t of catTokens) {
179
+ // Deduplicate by name
180
+ if (!mergedCategories[cat].some((e) => e.name === t.name)) {
181
+ mergedCategories[cat].push({ name: t.name, description: t.description });
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ if (total > 0) {
188
+ tokens = { prefix, total, categories: mergedCategories };
189
+ }
190
+ }
191
+ } catch {
192
+ // Token extraction failure is non-fatal
154
193
  }
155
194
 
156
195
  // Read package name for import statements
@@ -170,7 +209,8 @@ export async function buildSegments(
170
209
  generatedAt: new Date().toISOString(),
171
210
  ...(packageName && { packageName }),
172
211
  segments,
173
- ...(Object.keys(recipes).length > 0 && { recipes }),
212
+ ...(Object.keys(blocks).length > 0 && { blocks }),
213
+ ...(tokens && { tokens }),
174
214
  };
175
215
 
176
216
  const outputPath = resolve(configDir, config.outFile ?? BRAND.outFile);
@@ -66,7 +66,10 @@ export const BRAND = {
66
66
  /** MCP tool name prefix (e.g., "fragments_") */
67
67
  mcpToolPrefix: "fragments_",
68
68
 
69
- /** File extension for recipe definition files */
69
+ /** File extension for block definition files */
70
+ blockFileExtension: ".block.ts",
71
+
72
+ /** @deprecated Use blockFileExtension instead */
70
73
  recipeFileExtension: ".recipe.ts",
71
74
 
72
75
  /** Vite plugin namespace */
@@ -1,380 +1,2 @@
1
- import type { CompiledSegment, CompiledRecipe, PropDefinition } from "./types.js";
2
-
3
- /**
4
- * Placeholder patterns to filter out from usage text.
5
- * These are generated by the migrate tool and provide no value.
6
- */
7
- const PLACEHOLDER_PATTERNS = [
8
- /^\w+ component is needed$/i,
9
- /^Alternative component is more appropriate$/i,
10
- /^Use \w+ when you need/i,
11
- ];
12
-
13
- /**
14
- * Filter out placeholder text from usage arrays
15
- */
16
- function filterPlaceholders(items: string[]): string[] {
17
- return items.filter(item =>
18
- !PLACEHOLDER_PATTERNS.some(pattern => pattern.test(item.trim()))
19
- );
20
- }
21
-
22
- /**
23
- * Options for context generation
24
- */
25
- export interface ContextOptions {
26
- /** Output format */
27
- format?: "markdown" | "json";
28
-
29
- /** What to include in the output */
30
- include?: {
31
- /** Include prop details (default: true) */
32
- props?: boolean;
33
- /** Include variant list (default: true) */
34
- variants?: boolean;
35
- /** Include usage guidelines (default: true) */
36
- usage?: boolean;
37
- /** Include related components (default: false) */
38
- relations?: boolean;
39
- /** Include code examples (default: false) */
40
- code?: boolean;
41
- };
42
-
43
- /** Compact mode - minimal output for token efficiency */
44
- compact?: boolean;
45
- }
46
-
47
- /**
48
- * Result of context generation
49
- */
50
- export interface ContextResult {
51
- /** The generated context content */
52
- content: string;
53
- /** Estimated token count */
54
- tokenEstimate: number;
55
- }
56
-
57
- /**
58
- * Generate AI-ready context from compiled segments and optional recipes
59
- */
60
- export function generateContext(
61
- segments: CompiledSegment[],
62
- options: ContextOptions = {},
63
- recipes?: CompiledRecipe[]
64
- ): ContextResult {
65
- const format = options.format ?? "markdown";
66
- const compact = options.compact ?? false;
67
-
68
- const include = {
69
- props: options.include?.props ?? true,
70
- variants: options.include?.variants ?? true,
71
- usage: options.include?.usage ?? true,
72
- relations: options.include?.relations ?? false,
73
- code: options.include?.code ?? false,
74
- };
75
-
76
- // Sort segments by category, then name
77
- const sorted = [...segments].sort((a, b) => {
78
- const catCompare = a.meta.category.localeCompare(b.meta.category);
79
- if (catCompare !== 0) return catCompare;
80
- return a.meta.name.localeCompare(b.meta.name);
81
- });
82
-
83
- if (format === "json") {
84
- return generateJsonContext(sorted, include, compact, recipes);
85
- }
86
-
87
- return generateMarkdownContext(sorted, include, compact, recipes);
88
- }
89
-
90
- /**
91
- * Generate markdown context
92
- */
93
- function generateMarkdownContext(
94
- segments: CompiledSegment[],
95
- include: Required<NonNullable<ContextOptions["include"]>>,
96
- compact: boolean,
97
- recipes?: CompiledRecipe[]
98
- ): ContextResult {
99
- const lines: string[] = [];
100
-
101
- lines.push("# Design System Reference");
102
- lines.push("");
103
-
104
- // Quick reference table
105
- lines.push("## Quick Reference");
106
- lines.push("");
107
- lines.push("| Component | Category | Use For |");
108
- lines.push("|-----------|----------|---------|");
109
-
110
- for (const segment of segments) {
111
- const filteredWhen = filterPlaceholders(segment.usage.when);
112
- const useFor = filteredWhen.slice(0, 2).join(", ") || segment.meta.description;
113
- lines.push(`| ${segment.meta.name} | ${segment.meta.category} | ${truncate(useFor, 50)} |`);
114
- }
115
-
116
- lines.push("");
117
-
118
- // If compact mode, stop here
119
- if (compact) {
120
- const content = lines.join("\n");
121
- return {
122
- content,
123
- tokenEstimate: estimateTokens(content),
124
- };
125
- }
126
-
127
- // Full component documentation
128
- lines.push("## Components");
129
- lines.push("");
130
-
131
- for (const segment of segments) {
132
- lines.push(`### ${segment.meta.name}`);
133
- lines.push("");
134
-
135
- // Status line
136
- const statusParts = [`**Category:** ${segment.meta.category}`];
137
- if (segment.meta.status) {
138
- statusParts.push(`**Status:** ${segment.meta.status}`);
139
- }
140
- lines.push(statusParts.join(" | "));
141
- lines.push("");
142
-
143
- if (segment.meta.description) {
144
- lines.push(segment.meta.description);
145
- lines.push("");
146
- }
147
-
148
- // Usage guidelines (filter out placeholder text)
149
- const whenFiltered = filterPlaceholders(segment.usage.when);
150
- const whenNotFiltered = filterPlaceholders(segment.usage.whenNot);
151
-
152
- if (include.usage && (whenFiltered.length > 0 || whenNotFiltered.length > 0)) {
153
- if (whenFiltered.length > 0) {
154
- lines.push("**When to use:**");
155
- for (const when of whenFiltered) {
156
- lines.push(`- ${when}`);
157
- }
158
- lines.push("");
159
- }
160
-
161
- if (whenNotFiltered.length > 0) {
162
- lines.push("**When NOT to use:**");
163
- for (const whenNot of whenNotFiltered) {
164
- lines.push(`- ${whenNot}`);
165
- }
166
- lines.push("");
167
- }
168
- }
169
-
170
- // Props
171
- if (include.props && Object.keys(segment.props).length > 0) {
172
- lines.push("**Props:**");
173
- for (const [name, prop] of Object.entries(segment.props)) {
174
- lines.push(`- \`${name}\`: ${formatPropType(prop)}${prop.required ? " (required)" : ""}`);
175
- }
176
- lines.push("");
177
- }
178
-
179
- // Variants
180
- if (include.variants && segment.variants.length > 0) {
181
- const variantNames = segment.variants.map((v) => v.name).join(", ");
182
- lines.push(`**Variants:** ${variantNames}`);
183
- lines.push("");
184
-
185
- // Code examples
186
- if (include.code) {
187
- for (const variant of segment.variants) {
188
- if (variant.code) {
189
- lines.push(`*${variant.name}:*`);
190
- lines.push("```tsx");
191
- lines.push(variant.code);
192
- lines.push("```");
193
- lines.push("");
194
- }
195
- }
196
- }
197
- }
198
-
199
- // Relations
200
- if (include.relations && segment.relations && segment.relations.length > 0) {
201
- lines.push("**Related:**");
202
- for (const relation of segment.relations) {
203
- lines.push(`- ${relation.component} (${relation.relationship}): ${relation.note}`);
204
- }
205
- lines.push("");
206
- }
207
-
208
- lines.push("---");
209
- lines.push("");
210
- }
211
-
212
- // Recipes section
213
- if (recipes && recipes.length > 0) {
214
- lines.push("## Recipes");
215
- lines.push("");
216
- lines.push("Composition patterns showing how components wire together.");
217
- lines.push("");
218
-
219
- for (const recipe of recipes) {
220
- lines.push(`### ${recipe.name}`);
221
- lines.push("");
222
- lines.push(recipe.description);
223
- lines.push("");
224
- lines.push(`**Category:** ${recipe.category}`);
225
- lines.push(`**Components:** ${recipe.components.join(", ")}`);
226
- if (recipe.tags && recipe.tags.length > 0) {
227
- lines.push(`**Tags:** ${recipe.tags.join(", ")}`);
228
- }
229
- lines.push("");
230
- lines.push("```tsx");
231
- lines.push(recipe.code);
232
- lines.push("```");
233
- lines.push("");
234
- lines.push("---");
235
- lines.push("");
236
- }
237
- }
238
-
239
- const content = lines.join("\n");
240
- return {
241
- content,
242
- tokenEstimate: estimateTokens(content),
243
- };
244
- }
245
-
246
- /**
247
- * Generate JSON context
248
- */
249
- function generateJsonContext(
250
- segments: CompiledSegment[],
251
- include: Required<NonNullable<ContextOptions["include"]>>,
252
- compact: boolean,
253
- recipes?: CompiledRecipe[]
254
- ): ContextResult {
255
- const categories = [...new Set(segments.map((s) => s.meta.category))].sort();
256
-
257
- interface JsonComponent {
258
- category: string;
259
- description: string;
260
- status?: string;
261
- whenToUse?: string[];
262
- whenNotToUse?: string[];
263
- props?: Record<string, { type: string; required?: boolean; default?: unknown; description: string }>;
264
- variants?: string[];
265
- relations?: Array<{ component: string; relationship: string; note: string }>;
266
- }
267
-
268
- const components: Record<string, JsonComponent> = {};
269
-
270
- for (const segment of segments) {
271
- const component: JsonComponent = {
272
- category: segment.meta.category,
273
- description: segment.meta.description,
274
- };
275
-
276
- if (segment.meta.status) {
277
- component.status = segment.meta.status;
278
- }
279
-
280
- if (!compact) {
281
- if (include.usage) {
282
- const whenFiltered = filterPlaceholders(segment.usage.when);
283
- const whenNotFiltered = filterPlaceholders(segment.usage.whenNot);
284
- if (whenFiltered.length > 0) {
285
- component.whenToUse = whenFiltered;
286
- }
287
- if (whenNotFiltered.length > 0) {
288
- component.whenNotToUse = whenNotFiltered;
289
- }
290
- }
291
-
292
- if (include.props && Object.keys(segment.props).length > 0) {
293
- component.props = {};
294
- for (const [name, prop] of Object.entries(segment.props)) {
295
- component.props[name] = {
296
- type: formatPropType(prop),
297
- description: prop.description,
298
- };
299
- if (prop.required) {
300
- component.props[name].required = true;
301
- }
302
- if (prop.default !== undefined) {
303
- component.props[name].default = prop.default;
304
- }
305
- }
306
- }
307
-
308
- if (include.variants && segment.variants.length > 0) {
309
- component.variants = segment.variants.map((v) => v.name);
310
- }
311
-
312
- if (include.relations && segment.relations && segment.relations.length > 0) {
313
- component.relations = segment.relations.map((r) => ({
314
- component: r.component,
315
- relationship: r.relationship,
316
- note: r.note,
317
- }));
318
- }
319
- }
320
-
321
- components[segment.meta.name] = component;
322
- }
323
-
324
- // Build recipes map
325
- const recipesMap = recipes && recipes.length > 0
326
- ? Object.fromEntries(recipes.map(r => [r.name, {
327
- description: r.description,
328
- category: r.category,
329
- components: r.components,
330
- code: r.code,
331
- tags: r.tags,
332
- }]))
333
- : undefined;
334
-
335
- const output = {
336
- version: "1.0",
337
- generatedAt: new Date().toISOString(),
338
- summary: {
339
- totalComponents: segments.length,
340
- categories,
341
- ...(recipesMap && { totalRecipes: recipes!.length }),
342
- },
343
- components,
344
- ...(recipesMap && { recipes: recipesMap }),
345
- };
346
-
347
- const content = JSON.stringify(output, null, 2);
348
- return {
349
- content,
350
- tokenEstimate: estimateTokens(content),
351
- };
352
- }
353
-
354
- /**
355
- * Format a prop type for display
356
- */
357
- function formatPropType(prop: PropDefinition): string {
358
- if (prop.type === "enum" && prop.values) {
359
- return prop.values.map((v) => `"${v}"`).join(" | ");
360
- }
361
- if (prop.default !== undefined) {
362
- return `${prop.type} (default: ${JSON.stringify(prop.default)})`;
363
- }
364
- return prop.type;
365
- }
366
-
367
- /**
368
- * Truncate string to max length
369
- */
370
- function truncate(str: string, maxLength: number): string {
371
- if (str.length <= maxLength) return str;
372
- return str.slice(0, maxLength - 3) + "...";
373
- }
374
-
375
- /**
376
- * Estimate token count (rough approximation: ~4 chars per token)
377
- */
378
- function estimateTokens(text: string): number {
379
- return Math.ceil(text.length / 4);
380
- }
1
+ export { generateContext, filterPlaceholders, PLACEHOLDER_PATTERNS } from '@fragments-sdk/context/generate';
2
+ export type { ContextOptions, ContextResult } from '@fragments-sdk/context/generate';
@@ -1,5 +1,5 @@
1
- import type { SegmentDefinition, CompiledSegment, SegmentComponent, RecipeDefinition, CompiledRecipe } from './types.js';
2
- import { segmentDefinitionSchema, recipeDefinitionSchema } from './schema.js';
1
+ import type { SegmentDefinition, CompiledSegment, SegmentComponent, BlockDefinition, CompiledBlock } from './types.js';
2
+ import { segmentDefinitionSchema, blockDefinitionSchema } from './schema.js';
3
3
 
4
4
  /**
5
5
  * Define a segment for a component.
@@ -92,20 +92,20 @@ export function compileSegment(
92
92
  }
93
93
 
94
94
  /**
95
- * Define a composition recipe.
95
+ * Define a composition block.
96
96
  *
97
- * Recipes are pure data describing how design system components
97
+ * Blocks are pure data describing how design system components
98
98
  * wire together for common use cases.
99
99
  */
100
- export function defineRecipe(definition: RecipeDefinition): RecipeDefinition {
100
+ export function defineBlock(definition: BlockDefinition): BlockDefinition {
101
101
  if (process.env.NODE_ENV !== 'production') {
102
- const result = recipeDefinitionSchema.safeParse(definition);
102
+ const result = blockDefinitionSchema.safeParse(definition);
103
103
  if (!result.success) {
104
104
  const errors = result.error.errors
105
105
  .map((e) => ` - ${e.path.join('.')}: ${e.message}`)
106
106
  .join('\n');
107
107
  throw new Error(
108
- `Invalid recipe definition for "${definition.name || 'unknown'}":\n${errors}`
108
+ `Invalid block definition for "${definition.name || 'unknown'}":\n${errors}`
109
109
  );
110
110
  }
111
111
  }
@@ -114,12 +114,17 @@ export function defineRecipe(definition: RecipeDefinition): RecipeDefinition {
114
114
  }
115
115
 
116
116
  /**
117
- * Compile a recipe definition to JSON-serializable format.
117
+ * @deprecated Use defineBlock instead
118
118
  */
119
- export function compileRecipe(
120
- definition: RecipeDefinition,
119
+ export const defineRecipe = defineBlock;
120
+
121
+ /**
122
+ * Compile a block definition to JSON-serializable format.
123
+ */
124
+ export function compileBlock(
125
+ definition: BlockDefinition,
121
126
  filePath: string
122
- ): CompiledRecipe {
127
+ ): CompiledBlock {
123
128
  return {
124
129
  filePath,
125
130
  name: definition.name,
@@ -131,6 +136,11 @@ export function compileRecipe(
131
136
  };
132
137
  }
133
138
 
139
+ /**
140
+ * @deprecated Use compileBlock instead
141
+ */
142
+ export const compileRecipe = compileBlock;
143
+
134
144
  /**
135
145
  * Type helper for extracting props type from a component
136
146
  */