@fragments-sdk/cli 0.5.0 → 0.5.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.
Files changed (33) hide show
  1. package/dist/bin.js +7 -7
  2. package/dist/{chunk-UXRGD3DM.js → chunk-U4GQ2JTD.js} +2 -2
  3. package/dist/{chunk-B2TQKOLW.js → chunk-V7YLRR4C.js} +2 -238
  4. package/dist/chunk-V7YLRR4C.js.map +1 -0
  5. package/dist/{core-NJVKKLJ4.js → core-DKHB7FYV.js} +2 -2
  6. package/dist/{generate-OVGMDKCJ.js → generate-KL24VZVD.js} +2 -2
  7. package/dist/index.d.ts +2 -285
  8. package/dist/index.js +2 -2
  9. package/dist/{init-EOA7TTOR.js → init-NION5S3M.js} +5 -5
  10. package/dist/init-NION5S3M.js.map +1 -0
  11. package/dist/mcp-bin.js +26 -3
  12. package/dist/mcp-bin.js.map +1 -1
  13. package/dist/{scan-YN4LUDKY.js → scan-ESEXV7LF.js} +2 -2
  14. package/dist/{service-2T26CBWE.js → service-RWUMZ3EW.js} +2 -2
  15. package/dist/{static-viewer-CLJJRYHK.js → static-viewer-O37MJ5B6.js} +2 -2
  16. package/dist/{tokens-FHA2DO22.js → tokens-ITADYVPF.js} +2 -2
  17. package/dist/{viewer-XDPD52L7.js → viewer-YDGFDTK5.js} +11 -11
  18. package/package.json +3 -2
  19. package/src/commands/init.ts +2 -2
  20. package/src/core/context.ts +2 -380
  21. package/src/core/types.ts +11 -101
  22. package/src/mcp/__tests__/findFragmentsJson.test.ts +30 -0
  23. package/src/mcp/server.ts +40 -1
  24. package/dist/chunk-B2TQKOLW.js.map +0 -1
  25. package/dist/init-EOA7TTOR.js.map +0 -1
  26. /package/dist/{chunk-UXRGD3DM.js.map → chunk-U4GQ2JTD.js.map} +0 -0
  27. /package/dist/{core-NJVKKLJ4.js.map → core-DKHB7FYV.js.map} +0 -0
  28. /package/dist/{generate-OVGMDKCJ.js.map → generate-KL24VZVD.js.map} +0 -0
  29. /package/dist/{scan-YN4LUDKY.js.map → scan-ESEXV7LF.js.map} +0 -0
  30. /package/dist/{service-2T26CBWE.js.map → service-RWUMZ3EW.js.map} +0 -0
  31. /package/dist/{static-viewer-CLJJRYHK.js.map → static-viewer-O37MJ5B6.js.map} +0 -0
  32. /package/dist/{tokens-FHA2DO22.js.map → tokens-ITADYVPF.js.map} +0 -0
  33. /package/dist/{viewer-XDPD52L7.js.map → viewer-YDGFDTK5.js.map} +0 -0
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-2H2JAA3U.js";
10
10
  import {
11
11
  generateContext
12
- } from "./chunk-B2TQKOLW.js";
12
+ } from "./chunk-V7YLRR4C.js";
13
13
  import {
14
14
  BRAND
15
15
  } from "./chunk-ICAIQ57V.js";
@@ -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-2T26CBWE.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-2T26CBWE.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-2T26CBWE.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-2T26CBWE.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-2T26CBWE.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-2T26CBWE.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-NJVKKLJ4.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-2T26CBWE.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-2T26CBWE.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-XDPD52L7.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.5.0",
3
+ "version": "0.5.2",
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",
@@ -133,7 +133,7 @@ function generateConfig(options: {
133
133
  const includeStr = options.includePaths.map((p) => ` '${p}'`).join(",\n");
134
134
  const componentStr = options.componentPaths.map((p) => ` '${p}'`).join(",\n");
135
135
 
136
- return `import type { FragmentsConfig } from '@fragments/core';
136
+ return `import type { FragmentsConfig } from '@fragments-sdk/cli';
137
137
 
138
138
  const config: FragmentsConfig = {
139
139
  // Glob patterns for finding fragment/story files
@@ -215,7 +215,7 @@ export function Button({
215
215
  */
216
216
  function generateExampleFragment(): string {
217
217
  return `import React from 'react';
218
- import { defineFragment } from '@fragments/core';
218
+ import { defineFragment } from '@fragments-sdk/cli';
219
219
  import { Button } from './Button';
220
220
 
221
221
  export default defineFragment({
@@ -1,380 +1,2 @@
1
- import type { CompiledSegment, CompiledBlock, 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 blocks
59
- */
60
- export function generateContext(
61
- segments: CompiledSegment[],
62
- options: ContextOptions = {},
63
- blocks?: CompiledBlock[]
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, blocks);
85
- }
86
-
87
- return generateMarkdownContext(sorted, include, compact, blocks);
88
- }
89
-
90
- /**
91
- * Generate markdown context
92
- */
93
- function generateMarkdownContext(
94
- segments: CompiledSegment[],
95
- include: Required<NonNullable<ContextOptions["include"]>>,
96
- compact: boolean,
97
- blocks?: CompiledBlock[]
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
- // Blocks section
213
- if (blocks && blocks.length > 0) {
214
- lines.push("## Blocks");
215
- lines.push("");
216
- lines.push("Composition patterns showing how components wire together.");
217
- lines.push("");
218
-
219
- for (const block of blocks) {
220
- lines.push(`### ${block.name}`);
221
- lines.push("");
222
- lines.push(block.description);
223
- lines.push("");
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
- }
229
- lines.push("");
230
- lines.push("```tsx");
231
- lines.push(block.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
- blocks?: CompiledBlock[]
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 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
- }]))
333
- : undefined;
334
-
335
- const output = {
336
- version: "1.0",
337
- generatedAt: new Date().toISOString(),
338
- summary: {
339
- totalComponents: segments.length,
340
- categories,
341
- ...(blocksMap && { totalBlocks: blocks!.length }),
342
- },
343
- components,
344
- ...(blocksMap && { blocks: blocksMap }),
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';
package/src/core/types.ts CHANGED
@@ -695,44 +695,17 @@ export interface VerifyResult {
695
695
  };
696
696
  }
697
697
 
698
- /**
699
- * Compiled segment data (JSON-serializable for AI consumption)
700
- */
701
- export interface CompiledSegment {
702
- /** File path relative to project root */
703
- filePath: string;
704
-
705
- /** Component metadata */
706
- meta: SegmentMeta;
707
-
708
- /** Usage guidelines */
709
- usage: SegmentUsage;
710
-
711
- /** Props documentation (without render functions) */
712
- props: Record<string, PropDefinition>;
713
-
714
- /** Component relationships */
715
- relations?: ComponentRelation[];
716
-
717
- /** Variant names and descriptions (without render functions) */
718
- variants: Array<{
719
- name: string;
720
- description: string;
721
- code?: string;
722
- figma?: string;
723
- /** Args/props used to render this variant (for code generation) */
724
- args?: Record<string, unknown>;
725
- }>;
698
+ // Compiled types — re-exported from @fragments-sdk/context
699
+ export type {
700
+ CompiledSegment,
701
+ CompiledBlock,
702
+ CompiledTokenEntry,
703
+ CompiledTokenData,
704
+ CompiledSegmentsFile,
705
+ } from '@fragments-sdk/context/types';
726
706
 
727
- /** Agent-optimized contract metadata */
728
- contract?: SegmentContract;
729
-
730
- /** AI-specific metadata for playground context generation */
731
- ai?: AIMetadata;
732
-
733
- /** Provenance tracking (for generated segments) */
734
- _generated?: SegmentGenerated;
735
- }
707
+ // Re-export CompiledBlock under deprecated alias
708
+ import type { CompiledBlock as _CompiledBlock } from '@fragments-sdk/context/types';
736
709
 
737
710
  /**
738
711
  * Block definition — a named composition pattern showing how
@@ -752,70 +725,7 @@ export interface BlockDefinition {
752
725
  */
753
726
  export type RecipeDefinition = BlockDefinition;
754
727
 
755
- /**
756
- * Compiled block data (JSON-serializable for AI consumption)
757
- */
758
- export interface CompiledBlock {
759
- filePath: string;
760
- name: string;
761
- description: string;
762
- category: string;
763
- components: string[];
764
- code: string;
765
- tags?: string[];
766
- }
767
-
768
728
  /**
769
729
  * @deprecated Use CompiledBlock instead
770
730
  */
771
- export type CompiledRecipe = CompiledBlock;
772
-
773
- /**
774
- * A single token entry in the compiled output
775
- */
776
- export interface CompiledTokenEntry {
777
- /** CSS variable name (e.g., "--fui-color-accent") */
778
- name: string;
779
- /** Description from inline comment */
780
- description?: string;
781
- }
782
-
783
- /**
784
- * Compiled token data stored in fragments.json
785
- */
786
- export interface CompiledTokenData {
787
- /** Detected variable prefix (e.g., "--fui-") */
788
- prefix: string;
789
- /** Total number of tokens */
790
- total: number;
791
- /** Tokens grouped by category */
792
- categories: Record<string, CompiledTokenEntry[]>;
793
- }
794
-
795
- /**
796
- * The compiled segments.json structure
797
- */
798
- export interface CompiledSegmentsFile {
799
- /** Version of the schema */
800
- version: string;
801
-
802
- /** When this file was generated */
803
- generatedAt: string;
804
-
805
- /** Package name for import statements (read from package.json at build time) */
806
- packageName?: string;
807
-
808
- /** All compiled segments indexed by component name */
809
- segments: Record<string, CompiledSegment>;
810
-
811
- /** All compiled blocks indexed by block name */
812
- blocks?: Record<string, CompiledBlock>;
813
-
814
- /** Design tokens (CSS custom properties) extracted from style files */
815
- tokens?: CompiledTokenData;
816
-
817
- /**
818
- * @deprecated Use blocks instead
819
- */
820
- recipes?: Record<string, CompiledBlock>;
821
- }
731
+ export type CompiledRecipe = _CompiledBlock;
@@ -306,6 +306,36 @@ describe('findFragmentsJson', () => {
306
306
  });
307
307
  });
308
308
 
309
+ describe('exports map fallback', () => {
310
+ it('finds fragments.json when package exports block ./package.json access', () => {
311
+ // Simulate a package with an exports map that doesn't expose ./package.json
312
+ // Node's createRequire.resolve('pkg/package.json') will throw ERR_PACKAGE_PATH_NOT_EXPORTED
313
+ // The fallback resolves the main entry and walks up to find package.json
314
+ const depDir = join(root, 'node_modules', 'my-ui-lib');
315
+ mkdirSync(depDir, { recursive: true });
316
+
317
+ writeJson(join(root, 'package.json'), {
318
+ dependencies: { 'my-ui-lib': '^1.0.0' },
319
+ });
320
+ writeJson(join(depDir, 'package.json'), {
321
+ name: 'my-ui-lib',
322
+ main: './index.js',
323
+ // exports that block ./package.json:
324
+ exports: {
325
+ '.': './index.js',
326
+ './fragments.json': './fragments.json',
327
+ },
328
+ fragments: 'fragments.json',
329
+ });
330
+ writeFileSync(join(depDir, 'index.js'), 'module.exports = {}');
331
+ writeFileSync(join(depDir, 'fragments.json'), '{}');
332
+
333
+ const result = findFragmentsJson(root);
334
+ expect(result.length).toBe(1);
335
+ expect(result[0]).toBe(join(depDir, 'fragments.json'));
336
+ });
337
+ });
338
+
309
339
  describe('Phase 3: monorepo workspace discovery', () => {
310
340
  it('finds fragments via npm/yarn workspaces field', () => {
311
341
  // Root has workspaces but does NOT list @my-scope/ui as a dep