@fragments-sdk/cli 0.4.4 → 0.5.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.
Files changed (57) 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-G3M3MPQ6.js → chunk-B2TQKOLW.js} +157 -30
  7. package/dist/chunk-B2TQKOLW.js.map +1 -0
  8. package/dist/{chunk-AW7MWOUH.js → chunk-ICAIQ57V.js} +9 -5
  9. package/dist/chunk-ICAIQ57V.js.map +1 -0
  10. package/dist/{chunk-5ZYEOHYK.js → chunk-IOJE35DZ.js} +2 -2
  11. package/dist/{chunk-ZFKGX3QK.js → chunk-UXRGD3DM.js} +47 -14
  12. package/dist/chunk-UXRGD3DM.js.map +1 -0
  13. package/dist/{chunk-J4SI5RIH.js → chunk-XNWDI6UT.js} +4 -4
  14. package/dist/{core-LNXDLXDP.js → core-NJVKKLJ4.js} +11 -3
  15. package/dist/{generate-OIXXHOWR.js → generate-OVGMDKCJ.js} +4 -4
  16. package/dist/index.d.ts +30 -4
  17. package/dist/index.js +6 -6
  18. package/dist/{init-EVPXIDW4.js → init-EOA7TTOR.js} +4 -4
  19. package/dist/mcp-bin.js +266 -36
  20. package/dist/mcp-bin.js.map +1 -1
  21. package/dist/scan-YN4LUDKY.js +12 -0
  22. package/dist/{service-K52ORLCJ.js → service-2T26CBWE.js} +4 -4
  23. package/dist/{static-viewer-JNQIHA4B.js → static-viewer-CLJJRYHK.js} +4 -4
  24. package/dist/{test-USARUEFW.js → test-ECPEXFDN.js} +3 -3
  25. package/dist/{tokens-C6YHBOQE.js → tokens-FHA2DO22.js} +5 -5
  26. package/dist/{viewer-H7TVFT4E.js → viewer-XDPD52L7.js} +13 -13
  27. package/package.json +1 -1
  28. package/src/build.ts +53 -13
  29. package/src/core/constants.ts +4 -1
  30. package/src/core/context.ts +28 -28
  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 +46 -6
  40. package/src/mcp/server.ts +321 -39
  41. package/dist/chunk-5CKYLCJH.js.map +0 -1
  42. package/dist/chunk-AW7MWOUH.js.map +0 -1
  43. package/dist/chunk-G3M3MPQ6.js.map +0 -1
  44. package/dist/chunk-ZFKGX3QK.js.map +0 -1
  45. package/dist/scan-YVYD64GD.js +0 -12
  46. /package/dist/{chunk-NOTYONHY.js.map → chunk-2DJH4F4P.js.map} +0 -0
  47. /package/dist/{chunk-5ZYEOHYK.js.map → chunk-IOJE35DZ.js.map} +0 -0
  48. /package/dist/{chunk-J4SI5RIH.js.map → chunk-XNWDI6UT.js.map} +0 -0
  49. /package/dist/{core-LNXDLXDP.js.map → core-NJVKKLJ4.js.map} +0 -0
  50. /package/dist/{generate-OIXXHOWR.js.map → generate-OVGMDKCJ.js.map} +0 -0
  51. /package/dist/{init-EVPXIDW4.js.map → init-EOA7TTOR.js.map} +0 -0
  52. /package/dist/{scan-YVYD64GD.js.map → scan-YN4LUDKY.js.map} +0 -0
  53. /package/dist/{service-K52ORLCJ.js.map → service-2T26CBWE.js.map} +0 -0
  54. /package/dist/{static-viewer-JNQIHA4B.js.map → static-viewer-CLJJRYHK.js.map} +0 -0
  55. /package/dist/{test-USARUEFW.js.map → test-ECPEXFDN.js.map} +0 -0
  56. /package/dist/{tokens-C6YHBOQE.js.map → tokens-FHA2DO22.js.map} +0 -0
  57. /package/dist/{viewer-H7TVFT4E.js.map → viewer-XDPD52L7.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-B2TQKOLW.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-2T26CBWE.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-2T26CBWE.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-2T26CBWE.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-2T26CBWE.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-2T26CBWE.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-2T26CBWE.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-NJVKKLJ4.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-2T26CBWE.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-2T26CBWE.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-XDPD52L7.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.0",
4
4
  "description": "CLI, MCP server, and dev tools for Fragments design system",
5
5
  "type": "module",
6
6
  "bin": {
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,4 +1,4 @@
1
- import type { CompiledSegment, CompiledRecipe, PropDefinition } from "./types.js";
1
+ import type { CompiledSegment, CompiledBlock, PropDefinition } from "./types.js";
2
2
 
3
3
  /**
4
4
  * Placeholder patterns to filter out from usage text.
@@ -55,12 +55,12 @@ export interface ContextResult {
55
55
  }
56
56
 
57
57
  /**
58
- * Generate AI-ready context from compiled segments and optional recipes
58
+ * Generate AI-ready context from compiled segments and optional blocks
59
59
  */
60
60
  export function generateContext(
61
61
  segments: CompiledSegment[],
62
62
  options: ContextOptions = {},
63
- recipes?: CompiledRecipe[]
63
+ blocks?: CompiledBlock[]
64
64
  ): ContextResult {
65
65
  const format = options.format ?? "markdown";
66
66
  const compact = options.compact ?? false;
@@ -81,10 +81,10 @@ export function generateContext(
81
81
  });
82
82
 
83
83
  if (format === "json") {
84
- return generateJsonContext(sorted, include, compact, recipes);
84
+ return generateJsonContext(sorted, include, compact, blocks);
85
85
  }
86
86
 
87
- return generateMarkdownContext(sorted, include, compact, recipes);
87
+ return generateMarkdownContext(sorted, include, compact, blocks);
88
88
  }
89
89
 
90
90
  /**
@@ -94,7 +94,7 @@ function generateMarkdownContext(
94
94
  segments: CompiledSegment[],
95
95
  include: Required<NonNullable<ContextOptions["include"]>>,
96
96
  compact: boolean,
97
- recipes?: CompiledRecipe[]
97
+ blocks?: CompiledBlock[]
98
98
  ): ContextResult {
99
99
  const lines: string[] = [];
100
100
 
@@ -209,26 +209,26 @@ function generateMarkdownContext(
209
209
  lines.push("");
210
210
  }
211
211
 
212
- // Recipes section
213
- if (recipes && recipes.length > 0) {
214
- lines.push("## Recipes");
212
+ // Blocks section
213
+ if (blocks && blocks.length > 0) {
214
+ lines.push("## Blocks");
215
215
  lines.push("");
216
216
  lines.push("Composition patterns showing how components wire together.");
217
217
  lines.push("");
218
218
 
219
- for (const recipe of recipes) {
220
- lines.push(`### ${recipe.name}`);
219
+ for (const block of blocks) {
220
+ lines.push(`### ${block.name}`);
221
221
  lines.push("");
222
- lines.push(recipe.description);
222
+ lines.push(block.description);
223
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(", ")}`);
224
+ lines.push(`**Category:** ${block.category}`);
225
+ lines.push(`**Components:** ${block.components.join(", ")}`);
226
+ if (block.tags && block.tags.length > 0) {
227
+ lines.push(`**Tags:** ${block.tags.join(", ")}`);
228
228
  }
229
229
  lines.push("");
230
230
  lines.push("```tsx");
231
- lines.push(recipe.code);
231
+ lines.push(block.code);
232
232
  lines.push("```");
233
233
  lines.push("");
234
234
  lines.push("---");
@@ -250,7 +250,7 @@ function generateJsonContext(
250
250
  segments: CompiledSegment[],
251
251
  include: Required<NonNullable<ContextOptions["include"]>>,
252
252
  compact: boolean,
253
- recipes?: CompiledRecipe[]
253
+ blocks?: CompiledBlock[]
254
254
  ): ContextResult {
255
255
  const categories = [...new Set(segments.map((s) => s.meta.category))].sort();
256
256
 
@@ -321,14 +321,14 @@ function generateJsonContext(
321
321
  components[segment.meta.name] = component;
322
322
  }
323
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,
324
+ // Build blocks map
325
+ const blocksMap = blocks && blocks.length > 0
326
+ ? Object.fromEntries(blocks.map(b => [b.name, {
327
+ description: b.description,
328
+ category: b.category,
329
+ components: b.components,
330
+ code: b.code,
331
+ tags: b.tags,
332
332
  }]))
333
333
  : undefined;
334
334
 
@@ -338,10 +338,10 @@ function generateJsonContext(
338
338
  summary: {
339
339
  totalComponents: segments.length,
340
340
  categories,
341
- ...(recipesMap && { totalRecipes: recipes!.length }),
341
+ ...(blocksMap && { totalBlocks: blocks!.length }),
342
342
  },
343
343
  components,
344
- ...(recipesMap && { recipes: recipesMap }),
344
+ ...(blocksMap && { blocks: blocksMap }),
345
345
  };
346
346
 
347
347
  const content = JSON.stringify(output, null, 2);
@@ -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
  */
@@ -27,14 +27,18 @@ export interface DiscoveredComponent {
27
27
  }
28
28
 
29
29
  /**
30
- * Discover recipe files (*.recipe.ts) under the config directory
30
+ * Discover block files (*.block.ts) under the config directory.
31
+ * Also discovers legacy *.recipe.ts files for backward compatibility.
31
32
  */
32
- export async function discoverRecipeFiles(
33
+ export async function discoverBlockFiles(
33
34
  configDir: string,
34
35
  exclude?: string[]
35
36
  ): Promise<DiscoveredFile[]> {
36
- const pattern = `**/*${BRAND.recipeFileExtension}`;
37
- const files = await fg(pattern, {
37
+ const patterns = [
38
+ `**/*${BRAND.blockFileExtension}`,
39
+ `**/*${BRAND.recipeFileExtension}`,
40
+ ];
41
+ const files = await fg(patterns, {
38
42
  cwd: configDir,
39
43
  ignore: exclude ?? ['**/node_modules/**', '**/dist/**'],
40
44
  absolute: false,
@@ -46,6 +50,11 @@ export async function discoverRecipeFiles(
46
50
  }));
47
51
  }
48
52
 
53
+ /**
54
+ * @deprecated Use discoverBlockFiles instead
55
+ */
56
+ export const discoverRecipeFiles = discoverBlockFiles;
57
+
49
58
  /**
50
59
  * Discover segment files matching the config patterns
51
60
  */
@@ -296,6 +305,45 @@ export async function discoverComponentsFromBarrel(
296
305
  return components;
297
306
  }
298
307
 
308
+ /**
309
+ * Default glob patterns for discovering token files (SCSS/CSS with custom properties)
310
+ */
311
+ const DEFAULT_TOKEN_PATTERNS = [
312
+ 'src/**/tokens/**/_variables.scss',
313
+ 'src/**/tokens/**/variables.scss',
314
+ 'src/**/styles/**/variables.scss',
315
+ 'src/**/styles/**/tokens.scss',
316
+ 'src/**/styles/**/variables.css',
317
+ 'src/**/theme/**/_variables.scss',
318
+ 'src/**/theme/**/tokens.css',
319
+ ];
320
+
321
+ /**
322
+ * Discover token files (SCSS/CSS files containing CSS custom properties).
323
+ * Uses config.tokens.include patterns if provided, otherwise falls back
324
+ * to default patterns that match common project structures.
325
+ */
326
+ export async function discoverTokenFiles(
327
+ configDir: string,
328
+ patterns?: string[],
329
+ exclude?: string[]
330
+ ): Promise<DiscoveredFile[]> {
331
+ const searchPatterns = patterns && patterns.length > 0
332
+ ? patterns
333
+ : DEFAULT_TOKEN_PATTERNS;
334
+
335
+ const files = await fg(searchPatterns, {
336
+ cwd: configDir,
337
+ ignore: exclude ?? ['**/node_modules/**', '**/dist/**'],
338
+ absolute: false,
339
+ });
340
+
341
+ return files.map((relativePath) => ({
342
+ relativePath,
343
+ absolutePath: resolve(configDir, relativePath),
344
+ }));
345
+ }
346
+
299
347
  /**
300
348
  * Discover fragment files from installed packages that declare a "fragments" field
301
349
  * in their package.json. This allows consumer projects to see components from
package/src/core/index.ts CHANGED
@@ -27,8 +27,10 @@ export type {
27
27
  RegistryOptions,
28
28
  CompiledSegment,
29
29
  CompiledSegmentsFile,
30
- RecipeDefinition,
31
- CompiledRecipe,
30
+ BlockDefinition,
31
+ CompiledBlock,
32
+ RecipeDefinition, // @deprecated - use BlockDefinition
33
+ CompiledRecipe, // @deprecated - use CompiledBlock
32
34
  // Contract and provenance types
33
35
  SegmentContract,
34
36
  SegmentGenerated,
@@ -57,6 +59,9 @@ export type {
57
59
  FigmaTextContentMapping,
58
60
  // Token configuration
59
61
  TokenConfig,
62
+ // Compiled token types
63
+ CompiledTokenEntry,
64
+ CompiledTokenData,
60
65
  } from "./types.js";
61
66
 
62
67
  // Token types
@@ -88,13 +93,14 @@ export {
88
93
  segmentContractSchema,
89
94
  segmentGeneratedSchema,
90
95
  segmentBanSchema,
91
- recipeDefinitionSchema,
96
+ blockDefinitionSchema,
97
+ recipeDefinitionSchema, // @deprecated - use blockDefinitionSchema
92
98
  // AI metadata schema
93
99
  aiMetadataSchema,
94
100
  } from "./schema.js";
95
101
 
96
102
  // Main API
97
- export { defineSegment, defineFragment, compileSegment, defineRecipe, compileRecipe } from "./defineSegment.js";
103
+ export { defineSegment, defineFragment, compileSegment, defineBlock, compileBlock, defineRecipe, compileRecipe } from "./defineSegment.js";
98
104
  export type { InferProps } from "./defineSegment.js";
99
105
 
100
106
  // Story adapter (runtime conversion of Storybook modules)
@@ -143,6 +149,10 @@ export type {
143
149
  FragmentContextOptions,
144
150
  } from "./fragment-types.js";
145
151
 
152
+ // Token parsing
153
+ export { parseTokenFile } from "./token-parser.js";
154
+ export type { ParsedToken, TokenParseOutput } from "./token-parser.js";
155
+
146
156
  // Composition analysis
147
157
  export { analyzeComposition } from "./composition.js";
148
158
  export type {
@@ -14,6 +14,9 @@ export function defineSegment(def) {
14
14
  export function defineFragment(def) {
15
15
  return def;
16
16
  }
17
+ export function defineBlock(def) {
18
+ return def;
19
+ }
17
20
  export function defineRecipe(def) {
18
21
  return def;
19
22
  }
package/src/core/node.ts CHANGED
@@ -9,7 +9,8 @@ export { loadConfig, findConfigFile } from './config.js';
9
9
  // Discovery
10
10
  export {
11
11
  discoverSegmentFiles,
12
- discoverRecipeFiles,
12
+ discoverBlockFiles,
13
+ discoverRecipeFiles, // @deprecated - use discoverBlockFiles
13
14
  discoverComponentFiles,
14
15
  discoverInstalledFragments,
15
16
  extractComponentName,
@@ -17,6 +18,7 @@ export {
17
18
  discoverComponentsFromSource,
18
19
  discoverComponentsFromBarrel,
19
20
  discoverAllComponents,
21
+ discoverTokenFiles,
20
22
  } from './discovery.js';
21
23
  export type { DiscoveredFile, DiscoveredComponent } from './discovery.js';
22
24
 
@@ -375,7 +375,7 @@ function extractVariants(
375
375
 
376
376
  // Try to extract code property if present
377
377
  const codeProp = findProperty(element, "code");
378
- if (codeProp && ts.isStringLiteral(codeProp)) {
378
+ if (codeProp && (ts.isStringLiteral(codeProp) || ts.isNoSubstitutionTemplateLiteral(codeProp))) {
379
379
  variant.code = codeProp.text;
380
380
  }
381
381
 
@@ -149,9 +149,9 @@ export const aiMetadataSchema = z.object({
149
149
  });
150
150
 
151
151
  /**
152
- * Schema for recipe definitions
152
+ * Schema for block definitions
153
153
  */
154
- export const recipeDefinitionSchema = z.object({
154
+ export const blockDefinitionSchema = z.object({
155
155
  name: z.string().min(1),
156
156
  description: z.string().min(1),
157
157
  category: z.string().min(1),
@@ -191,3 +191,8 @@ export const segmentsConfigSchema = z.object({
191
191
  include: z.array(z.string()).min(1),
192
192
  }).passthrough().optional(),
193
193
  });
194
+
195
+ /**
196
+ * @deprecated Use blockDefinitionSchema instead
197
+ */
198
+ export const recipeDefinitionSchema = blockDefinitionSchema;