@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.
- package/README.md +1 -1
- package/dist/bin.js +12 -12
- package/dist/{chunk-NOTYONHY.js → chunk-2DJH4F4P.js} +2 -2
- package/dist/{chunk-5CKYLCJH.js → chunk-2H2JAA3U.js} +35 -7
- package/dist/chunk-2H2JAA3U.js.map +1 -0
- package/dist/{chunk-AW7MWOUH.js → chunk-ICAIQ57V.js} +9 -5
- package/dist/chunk-ICAIQ57V.js.map +1 -0
- package/dist/{chunk-5ZYEOHYK.js → chunk-IOJE35DZ.js} +2 -2
- package/dist/{chunk-ZFKGX3QK.js → chunk-U4GQ2JTD.js} +47 -14
- package/dist/chunk-U4GQ2JTD.js.map +1 -0
- package/dist/{chunk-G3M3MPQ6.js → chunk-V7YLRR4C.js} +135 -244
- package/dist/chunk-V7YLRR4C.js.map +1 -0
- package/dist/{chunk-J4SI5RIH.js → chunk-XNWDI6UT.js} +4 -4
- package/dist/{core-LNXDLXDP.js → core-DKHB7FYV.js} +11 -3
- package/dist/{generate-OIXXHOWR.js → generate-KL24VZVD.js} +4 -4
- package/dist/index.d.ts +2 -259
- package/dist/index.js +6 -6
- package/dist/{init-EVPXIDW4.js → init-NZB55B5O.js} +4 -4
- package/dist/mcp-bin.js +290 -37
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-ESEXV7LF.js +12 -0
- package/dist/{service-K52ORLCJ.js → service-RWUMZ3EW.js} +4 -4
- package/dist/{static-viewer-JNQIHA4B.js → static-viewer-O37MJ5B6.js} +4 -4
- package/dist/{test-USARUEFW.js → test-ECPEXFDN.js} +3 -3
- package/dist/{tokens-C6YHBOQE.js → tokens-ITADYVPF.js} +5 -5
- package/dist/{viewer-H7TVFT4E.js → viewer-YDGFDTK5.js} +13 -13
- package/package.json +3 -2
- package/src/build.ts +53 -13
- package/src/core/constants.ts +4 -1
- package/src/core/context.ts +2 -380
- package/src/core/defineSegment.ts +21 -11
- package/src/core/discovery.ts +52 -4
- package/src/core/index.ts +14 -4
- package/src/core/loader.ts +3 -0
- package/src/core/node.ts +3 -1
- package/src/core/parser.ts +1 -1
- package/src/core/schema.ts +7 -2
- package/src/core/token-parser.ts +211 -0
- package/src/core/types.ts +16 -66
- package/src/mcp/__tests__/findFragmentsJson.test.ts +30 -0
- package/src/mcp/server.ts +361 -40
- package/dist/chunk-5CKYLCJH.js.map +0 -1
- package/dist/chunk-AW7MWOUH.js.map +0 -1
- package/dist/chunk-G3M3MPQ6.js.map +0 -1
- package/dist/chunk-ZFKGX3QK.js.map +0 -1
- package/dist/scan-YVYD64GD.js +0 -12
- /package/dist/{chunk-NOTYONHY.js.map → chunk-2DJH4F4P.js.map} +0 -0
- /package/dist/{chunk-5ZYEOHYK.js.map → chunk-IOJE35DZ.js.map} +0 -0
- /package/dist/{chunk-J4SI5RIH.js.map → chunk-XNWDI6UT.js.map} +0 -0
- /package/dist/{core-LNXDLXDP.js.map → core-DKHB7FYV.js.map} +0 -0
- /package/dist/{generate-OIXXHOWR.js.map → generate-KL24VZVD.js.map} +0 -0
- /package/dist/{init-EVPXIDW4.js.map → init-NZB55B5O.js.map} +0 -0
- /package/dist/{scan-YVYD64GD.js.map → scan-ESEXV7LF.js.map} +0 -0
- /package/dist/{service-K52ORLCJ.js.map → service-RWUMZ3EW.js.map} +0 -0
- /package/dist/{static-viewer-JNQIHA4B.js.map → static-viewer-O37MJ5B6.js.map} +0 -0
- /package/dist/{test-USARUEFW.js.map → test-ECPEXFDN.js.map} +0 -0
- /package/dist/{tokens-C6YHBOQE.js.map → tokens-ITADYVPF.js.map} +0 -0
- /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-
|
|
9
|
+
} from "./chunk-2H2JAA3U.js";
|
|
10
10
|
import {
|
|
11
11
|
generateContext
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-V7YLRR4C.js";
|
|
13
13
|
import {
|
|
14
14
|
BRAND
|
|
15
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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
|
-
|
|
8
|
+
CompiledBlock,
|
|
9
|
+
CompiledTokenData,
|
|
9
10
|
} from "./core/index.js";
|
|
10
|
-
import { BRAND,
|
|
11
|
-
import type {
|
|
11
|
+
import { BRAND, compileBlock, parseTokenFile } from "./core/index.js";
|
|
12
|
+
import type { BlockDefinition } from "./core/index.js";
|
|
12
13
|
import {
|
|
13
14
|
discoverSegmentFiles,
|
|
14
|
-
|
|
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
|
|
128
|
-
const
|
|
129
|
+
// Discover and compile block files
|
|
130
|
+
const blocks: Record<string, CompiledBlock> = {};
|
|
129
131
|
try {
|
|
130
|
-
const
|
|
131
|
-
for (const file of
|
|
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 =
|
|
143
|
-
|
|
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
|
|
150
|
+
warning: `Failed to load block: ${error instanceof Error ? error.message : String(error)}`,
|
|
149
151
|
});
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
} catch {
|
|
153
|
-
//
|
|
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(
|
|
212
|
+
...(Object.keys(blocks).length > 0 && { blocks }),
|
|
213
|
+
...(tokens && { tokens }),
|
|
174
214
|
};
|
|
175
215
|
|
|
176
216
|
const outputPath = resolve(configDir, config.outFile ?? BRAND.outFile);
|
package/src/core/constants.ts
CHANGED
|
@@ -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
|
|
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 */
|
package/src/core/context.ts
CHANGED
|
@@ -1,380 +1,2 @@
|
|
|
1
|
-
|
|
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,
|
|
2
|
-
import { segmentDefinitionSchema,
|
|
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
|
|
95
|
+
* Define a composition block.
|
|
96
96
|
*
|
|
97
|
-
*
|
|
97
|
+
* Blocks are pure data describing how design system components
|
|
98
98
|
* wire together for common use cases.
|
|
99
99
|
*/
|
|
100
|
-
export function
|
|
100
|
+
export function defineBlock(definition: BlockDefinition): BlockDefinition {
|
|
101
101
|
if (process.env.NODE_ENV !== 'production') {
|
|
102
|
-
const result =
|
|
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
|
|
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
|
-
*
|
|
117
|
+
* @deprecated Use defineBlock instead
|
|
118
118
|
*/
|
|
119
|
-
export
|
|
120
|
-
|
|
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
|
-
):
|
|
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
|
*/
|