@fragments-sdk/cli 0.6.0 → 0.7.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/dist/bin.js +529 -285
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-F7ITZPDJ.js → chunk-32VIEOQY.js} +18 -18
- package/dist/chunk-32VIEOQY.js.map +1 -0
- package/dist/{chunk-SSLQXHNX.js → chunk-5ITIP3ES.js} +27 -27
- package/dist/chunk-5ITIP3ES.js.map +1 -0
- package/dist/{chunk-RVRTRESS.js → chunk-DQHWLAUV.js} +29 -29
- package/dist/chunk-DQHWLAUV.js.map +1 -0
- package/dist/{chunk-Q7GOHVOK.js → chunk-GCZMFLDI.js} +67 -32
- package/dist/chunk-GCZMFLDI.js.map +1 -0
- package/dist/{chunk-6JBGU74P.js → chunk-GHYYFAQN.js} +23 -23
- package/dist/chunk-GHYYFAQN.js.map +1 -0
- package/dist/{chunk-NWQ4CJOQ.js → chunk-GKX2HPZ6.js} +40 -40
- package/dist/chunk-GKX2HPZ6.js.map +1 -0
- package/dist/{chunk-D35RGPAG.js → chunk-U6VTHBNI.js} +499 -83
- package/dist/chunk-U6VTHBNI.js.map +1 -0
- package/dist/{core-SKRPJQZG.js → core-SFHPYR5H.js} +24 -26
- package/dist/{generate-7AF7WRVK.js → generate-54GJAWUY.js} +5 -5
- package/dist/generate-54GJAWUY.js.map +1 -0
- package/dist/index.d.ts +23 -27
- package/dist/index.js +10 -10
- package/dist/{init-WKGDPYI4.js → init-EIM5WNMP.js} +5 -5
- package/dist/{init-WKGDPYI4.js.map → init-EIM5WNMP.js.map} +1 -1
- package/dist/mcp-bin.js +73 -73
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-KQBKUS64.js +12 -0
- package/dist/{service-F3E4JJM7.js → service-ED2LNCTU.js} +6 -6
- package/dist/{static-viewer-4LQZ5AGA.js → static-viewer-Q4F4QP5M.js} +4 -4
- package/dist/{test-CJDNJTPZ.js → test-6VN2DA3S.js} +19 -19
- package/dist/test-6VN2DA3S.js.map +1 -0
- package/dist/{tokens-JAJABYXP.js → tokens-P2B7ZAM3.js} +5 -5
- package/dist/{viewer-R3Q6WAMJ.js → viewer-GM7IQPPB.js} +199 -199
- package/dist/viewer-GM7IQPPB.js.map +1 -0
- package/package.json +2 -2
- package/src/ai.ts +5 -5
- package/src/analyze.ts +11 -11
- package/src/bin.ts +24 -1
- package/src/build.ts +64 -21
- package/src/commands/a11y.ts +6 -6
- package/src/commands/add.ts +11 -11
- package/src/commands/audit.ts +4 -4
- package/src/commands/baseline.ts +3 -3
- package/src/commands/build.ts +8 -8
- package/src/commands/compare.ts +20 -20
- package/src/commands/context.ts +16 -16
- package/src/commands/enhance.ts +36 -36
- package/src/commands/generate.ts +1 -1
- package/src/commands/graph.ts +274 -0
- package/src/commands/init.ts +1 -1
- package/src/commands/link/figma.ts +82 -82
- package/src/commands/link/index.ts +3 -3
- package/src/commands/link/storybook.ts +9 -9
- package/src/commands/list.ts +2 -2
- package/src/commands/reset.ts +15 -15
- package/src/commands/scan.ts +27 -27
- package/src/commands/storygen.ts +24 -24
- package/src/commands/validate.ts +2 -2
- package/src/commands/verify.ts +8 -8
- package/src/core/auto-props.ts +4 -4
- package/src/core/composition.test.ts +36 -36
- package/src/core/composition.ts +83 -20
- package/src/core/config.ts +6 -6
- package/src/core/{defineSegment.ts → defineFragment.ts} +16 -22
- package/src/core/discovery.ts +6 -6
- package/src/core/figma.ts +2 -2
- package/src/core/graph-extractor.test.ts +542 -0
- package/src/core/graph-extractor.ts +601 -0
- package/src/core/importAnalyzer.ts +6 -1
- package/src/core/index.ts +22 -23
- package/src/core/loader.ts +22 -22
- package/src/core/node.ts +5 -5
- package/src/core/parser.ts +31 -31
- package/src/core/previewLoader.ts +1 -1
- package/src/core/schema.ts +16 -16
- package/src/core/storyAdapter.test.ts +87 -87
- package/src/core/storyAdapter.ts +16 -16
- package/src/core/types.ts +21 -26
- package/src/diff.ts +22 -22
- package/src/index.ts +2 -2
- package/src/mcp/server.ts +80 -80
- package/src/migrate/__tests__/utils/utils.test.ts +3 -3
- package/src/migrate/bin.ts +4 -4
- package/src/migrate/converter.ts +16 -16
- package/src/migrate/index.ts +3 -3
- package/src/migrate/migrate.ts +3 -3
- package/src/migrate/parser.ts +8 -8
- package/src/migrate/report.ts +2 -2
- package/src/migrate/types.ts +4 -4
- package/src/screenshot.ts +22 -22
- package/src/service/__tests__/props-extractor.test.ts +15 -15
- package/src/service/analytics.ts +39 -39
- package/src/service/enhance/codebase-scanner.ts +1 -1
- package/src/service/enhance/index.ts +1 -1
- package/src/service/enhance/props-extractor.ts +2 -2
- package/src/service/enhance/types.ts +2 -2
- package/src/service/index.ts +2 -2
- package/src/service/metrics-store.ts +1 -1
- package/src/service/patch-generator.ts +1 -1
- package/src/setup.ts +52 -52
- package/src/shared/dev-server-client.ts +7 -7
- package/src/shared/fragment-loader.ts +59 -0
- package/src/shared/index.ts +1 -1
- package/src/shared/types.ts +4 -4
- package/src/static-viewer.ts +35 -35
- package/src/test/discovery.ts +6 -6
- package/src/test/index.ts +5 -5
- package/src/test/reporters/console.ts +1 -1
- package/src/test/reporters/junit.ts +1 -1
- package/src/test/runner.ts +7 -7
- package/src/test/types.ts +3 -3
- package/src/test/watch.ts +9 -9
- package/src/validators.ts +26 -26
- package/src/viewer/__tests__/render-utils.test.ts +28 -28
- package/src/viewer/__tests__/viewer-integration.test.ts +4 -4
- package/src/viewer/cli/health.ts +26 -26
- package/src/viewer/components/App.tsx +201 -103
- package/src/viewer/components/BottomPanel.tsx +17 -17
- package/src/viewer/components/CodePanel.tsx +3 -3
- package/src/viewer/components/CommandPalette.tsx +11 -11
- package/src/viewer/components/ComponentGraph.tsx +28 -28
- package/src/viewer/components/ComponentHeader.tsx +2 -2
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/FigmaEmbed.tsx +9 -9
- package/src/viewer/components/HealthDashboard.tsx +17 -17
- package/src/viewer/components/Icons.tsx +53 -1
- package/src/viewer/components/InteractionsPanel.tsx +2 -2
- package/src/viewer/components/IsolatedPreviewFrame.tsx +6 -6
- package/src/viewer/components/IsolatedRender.tsx +10 -10
- package/src/viewer/components/Layout.tsx +7 -3
- package/src/viewer/components/LeftSidebar.tsx +92 -114
- package/src/viewer/components/MultiViewportPreview.tsx +14 -14
- package/src/viewer/components/PreviewArea.tsx +11 -11
- package/src/viewer/components/PreviewFrameHost.tsx +77 -48
- package/src/viewer/components/PreviewToolbar.tsx +57 -10
- package/src/viewer/components/RightSidebar.tsx +9 -9
- package/src/viewer/components/Sidebar.tsx +17 -17
- package/src/viewer/components/StoryRenderer.tsx +2 -2
- package/src/viewer/components/TokenStylePanel.tsx +1 -1
- package/src/viewer/components/UsageSection.tsx +2 -2
- package/src/viewer/components/VariantMatrix.tsx +11 -11
- package/src/viewer/components/VariantRenderer.tsx +3 -3
- package/src/viewer/components/VariantTabs.tsx +2 -2
- package/src/viewer/components/ViewportSelector.tsx +56 -45
- package/src/viewer/components/_future/CreatePage.tsx +6 -6
- package/src/viewer/composition-renderer.ts +11 -11
- package/src/viewer/constants/ui.ts +4 -4
- package/src/viewer/entry.tsx +40 -40
- package/src/viewer/hooks/useFigmaIntegration.ts +1 -1
- package/src/viewer/hooks/usePreviewBridge.ts +5 -5
- package/src/viewer/hooks/useUrlState.ts +6 -6
- package/src/viewer/index.ts +2 -2
- package/src/viewer/intelligence/healthReport.ts +17 -17
- package/src/viewer/intelligence/styleDrift.ts +1 -1
- package/src/viewer/intelligence/usageScanner.ts +1 -1
- package/src/viewer/preview-frame.html +22 -13
- package/src/viewer/render-template.html +1 -1
- package/src/viewer/render-utils.ts +21 -21
- package/src/viewer/server.ts +18 -18
- package/src/viewer/styles/globals.css +42 -81
- package/src/viewer/utils/detectRelationships.ts +22 -22
- package/src/viewer/vite-plugin.ts +213 -213
- package/dist/chunk-6JBGU74P.js.map +0 -1
- package/dist/chunk-D35RGPAG.js.map +0 -1
- package/dist/chunk-F7ITZPDJ.js.map +0 -1
- package/dist/chunk-NWQ4CJOQ.js.map +0 -1
- package/dist/chunk-Q7GOHVOK.js.map +0 -1
- package/dist/chunk-RVRTRESS.js.map +0 -1
- package/dist/chunk-SSLQXHNX.js.map +0 -1
- package/dist/generate-7AF7WRVK.js.map +0 -1
- package/dist/scan-K6JNMCGM.js +0 -12
- package/dist/test-CJDNJTPZ.js.map +0 -1
- package/dist/viewer-R3Q6WAMJ.js.map +0 -1
- package/src/shared/segment-loader.ts +0 -59
- /package/dist/{core-SKRPJQZG.js.map → core-SFHPYR5H.js.map} +0 -0
- /package/dist/{scan-K6JNMCGM.js.map → scan-KQBKUS64.js.map} +0 -0
- /package/dist/{service-F3E4JJM7.js.map → service-ED2LNCTU.js.map} +0 -0
- /package/dist/{static-viewer-4LQZ5AGA.js.map → static-viewer-Q4F4QP5M.js.map} +0 -0
- /package/dist/{tokens-JAJABYXP.js.map → tokens-P2B7ZAM3.js.map} +0 -0
package/src/build.ts
CHANGED
|
@@ -2,20 +2,20 @@ import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
|
2
2
|
import { resolve, join } from "node:path";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
FragmentsConfig,
|
|
6
|
+
CompiledFragmentsFile,
|
|
7
|
+
CompiledFragment,
|
|
8
8
|
CompiledBlock,
|
|
9
9
|
CompiledTokenData,
|
|
10
10
|
} from "./core/index.js";
|
|
11
11
|
import { BRAND, compileBlock, parseTokenFile } from "./core/index.js";
|
|
12
12
|
import type { BlockDefinition } from "./core/index.js";
|
|
13
13
|
import {
|
|
14
|
-
|
|
14
|
+
discoverFragmentFiles,
|
|
15
15
|
discoverBlockFiles,
|
|
16
16
|
discoverTokenFiles,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
parseFragmentFile,
|
|
18
|
+
loadFragmentFile,
|
|
19
19
|
generateRegistry,
|
|
20
20
|
generateContextMd,
|
|
21
21
|
} from "./core/node.js";
|
|
@@ -24,8 +24,10 @@ import {
|
|
|
24
24
|
resolveComponentSourcePath,
|
|
25
25
|
type AutoDetectedPropDefinition,
|
|
26
26
|
} from "./core/auto-props.js";
|
|
27
|
+
import { buildComponentGraph } from "./core/graph-extractor.js";
|
|
28
|
+
import { serializeGraph } from "@fragments-sdk/context/graph";
|
|
27
29
|
|
|
28
|
-
type CompiledProp =
|
|
30
|
+
type CompiledProp = CompiledFragment["props"][string];
|
|
29
31
|
|
|
30
32
|
function normalizeParsedProps(
|
|
31
33
|
parsedProps: Record<string, Partial<CompiledProp>>
|
|
@@ -72,7 +74,7 @@ function mergeDocumentedAndAutoProps(
|
|
|
72
74
|
export interface BuildResult {
|
|
73
75
|
success: boolean;
|
|
74
76
|
outputPath: string;
|
|
75
|
-
|
|
77
|
+
fragmentCount: number;
|
|
76
78
|
errors: Array<{ file: string; error: string }>;
|
|
77
79
|
warnings: Array<{ file: string; warning: string }>;
|
|
78
80
|
}
|
|
@@ -83,14 +85,14 @@ export interface BuildResult {
|
|
|
83
85
|
* Uses AST parsing to extract metadata WITHOUT executing fragment files.
|
|
84
86
|
* This means the build works without any project dependencies installed.
|
|
85
87
|
*/
|
|
86
|
-
export async function
|
|
87
|
-
config:
|
|
88
|
+
export async function buildFragments(
|
|
89
|
+
config: FragmentsConfig,
|
|
88
90
|
configDir: string
|
|
89
91
|
): Promise<BuildResult> {
|
|
90
|
-
const files = await
|
|
92
|
+
const files = await discoverFragmentFiles(config, configDir);
|
|
91
93
|
const errors: Array<{ file: string; error: string }> = [];
|
|
92
94
|
const warnings: Array<{ file: string; warning: string }> = [];
|
|
93
|
-
const
|
|
95
|
+
const fragments: CompiledFragmentsFile["fragments"] = {};
|
|
94
96
|
|
|
95
97
|
for (const file of files) {
|
|
96
98
|
try {
|
|
@@ -98,7 +100,7 @@ export async function buildSegments(
|
|
|
98
100
|
const content = await readFile(file.absolutePath, "utf-8");
|
|
99
101
|
|
|
100
102
|
// Parse using AST (no execution)
|
|
101
|
-
const parsed =
|
|
103
|
+
const parsed = parseFragmentFile(content, file.relativePath);
|
|
102
104
|
|
|
103
105
|
// Collect warnings
|
|
104
106
|
for (const warning of parsed.warnings) {
|
|
@@ -169,7 +171,7 @@ export async function buildSegments(
|
|
|
169
171
|
}
|
|
170
172
|
|
|
171
173
|
// Build compiled fragment from parsed metadata
|
|
172
|
-
const compiled:
|
|
174
|
+
const compiled: CompiledFragment = {
|
|
173
175
|
filePath: file.relativePath,
|
|
174
176
|
meta: {
|
|
175
177
|
name: parsed.meta.name,
|
|
@@ -208,7 +210,7 @@ export async function buildSegments(
|
|
|
208
210
|
...(parsed.ai && { ai: parsed.ai }),
|
|
209
211
|
};
|
|
210
212
|
|
|
211
|
-
|
|
213
|
+
fragments[parsed.meta.name] = compiled;
|
|
212
214
|
} catch (error) {
|
|
213
215
|
errors.push({
|
|
214
216
|
file: file.relativePath,
|
|
@@ -223,9 +225,9 @@ export async function buildSegments(
|
|
|
223
225
|
const blockFiles = await discoverBlockFiles(configDir, config.exclude);
|
|
224
226
|
for (const file of blockFiles) {
|
|
225
227
|
try {
|
|
226
|
-
//
|
|
228
|
+
// loadFragmentFile uses esbuild to bundle+evaluate, returns default export
|
|
227
229
|
// CJS/ESM interop may double-wrap the default export
|
|
228
|
-
let raw = await
|
|
230
|
+
let raw = await loadFragmentFile(file.absolutePath) as unknown as Record<string, unknown> | null;
|
|
229
231
|
// Unwrap double-default from CJS interop
|
|
230
232
|
if (raw && 'default' in raw && typeof raw.default === 'object') {
|
|
231
233
|
raw = raw.default as Record<string, unknown>;
|
|
@@ -295,13 +297,54 @@ export async function buildSegments(
|
|
|
295
297
|
}
|
|
296
298
|
}
|
|
297
299
|
|
|
298
|
-
|
|
300
|
+
// Build component graph for AI structural queries
|
|
301
|
+
// Derive component directory from configDir (typically src/components/)
|
|
302
|
+
const componentDir = resolve(configDir, "src", "components");
|
|
303
|
+
let graphData: ReturnType<typeof serializeGraph> | undefined;
|
|
304
|
+
try {
|
|
305
|
+
const graphResult = await buildComponentGraph(fragments, blocks, componentDir);
|
|
306
|
+
|
|
307
|
+
// Auto-enrich fragments with detected metadata
|
|
308
|
+
for (const [name, fragment] of Object.entries(fragments)) {
|
|
309
|
+
const detected = graphResult.autoDetected.get(name);
|
|
310
|
+
if (!detected) continue;
|
|
311
|
+
|
|
312
|
+
if (!fragment.ai) fragment.ai = {};
|
|
313
|
+
if (!fragment.ai.subComponents && detected.subComponents) {
|
|
314
|
+
fragment.ai.subComponents = detected.subComponents;
|
|
315
|
+
}
|
|
316
|
+
if (!fragment.ai.compositionPattern && detected.compositionPattern) {
|
|
317
|
+
fragment.ai.compositionPattern = detected.compositionPattern;
|
|
318
|
+
}
|
|
319
|
+
if (!fragment.ai.commonPatterns && detected.commonPatterns) {
|
|
320
|
+
fragment.ai.commonPatterns = detected.commonPatterns;
|
|
321
|
+
}
|
|
322
|
+
if (!fragment.ai.requiredChildren && detected.requiredChildren) {
|
|
323
|
+
fragment.ai.requiredChildren = detected.requiredChildren;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Report drift warnings
|
|
328
|
+
for (const w of graphResult.warnings) {
|
|
329
|
+
warnings.push({ file: "graph", warning: w });
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
graphData = serializeGraph(graphResult.graph);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
warnings.push({
|
|
335
|
+
file: "graph",
|
|
336
|
+
warning: `Graph extraction failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const output: CompiledFragmentsFile = {
|
|
299
341
|
version: "1.0.0",
|
|
300
342
|
generatedAt: new Date().toISOString(),
|
|
301
343
|
...(packageName && { packageName }),
|
|
302
|
-
|
|
344
|
+
fragments,
|
|
303
345
|
...(Object.keys(blocks).length > 0 && { blocks }),
|
|
304
346
|
...(tokens && { tokens }),
|
|
347
|
+
...(graphData && { graph: graphData }),
|
|
305
348
|
};
|
|
306
349
|
|
|
307
350
|
const outputPath = resolve(configDir, config.outFile ?? BRAND.outFile);
|
|
@@ -310,7 +353,7 @@ export async function buildSegments(
|
|
|
310
353
|
return {
|
|
311
354
|
success: errors.length === 0,
|
|
312
355
|
outputPath,
|
|
313
|
-
|
|
356
|
+
fragmentCount: Object.keys(fragments).length,
|
|
314
357
|
errors,
|
|
315
358
|
warnings,
|
|
316
359
|
};
|
|
@@ -338,7 +381,7 @@ export interface FragmentsBuildResult {
|
|
|
338
381
|
* - .fragments/context.md - AI-ready consolidated context file
|
|
339
382
|
*/
|
|
340
383
|
export async function buildFragmentsDir(
|
|
341
|
-
config:
|
|
384
|
+
config: FragmentsConfig,
|
|
342
385
|
configDir: string
|
|
343
386
|
): Promise<FragmentsBuildResult> {
|
|
344
387
|
const fragmentsDir = join(configDir, BRAND.dataDir);
|
package/src/commands/a11y.ts
CHANGED
|
@@ -229,10 +229,10 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
|
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
// Fetch all
|
|
233
|
-
const
|
|
232
|
+
// Fetch all fragments
|
|
233
|
+
const fragments = await client.getFragments();
|
|
234
234
|
|
|
235
|
-
if (
|
|
235
|
+
if (fragments.length === 0) {
|
|
236
236
|
if (isJsonOutput) {
|
|
237
237
|
console.log(JSON.stringify({ error: 'No fragments found', components: [] }));
|
|
238
238
|
} else {
|
|
@@ -254,11 +254,11 @@ export async function a11y(options: A11yOptions = {}): Promise<A11ySummary> {
|
|
|
254
254
|
|
|
255
255
|
// Filter to specific component if requested
|
|
256
256
|
const componentsToCheck = component
|
|
257
|
-
?
|
|
258
|
-
:
|
|
257
|
+
? fragments.filter(s => s.name.toLowerCase() === component.toLowerCase())
|
|
258
|
+
: fragments;
|
|
259
259
|
|
|
260
260
|
if (component && componentsToCheck.length === 0) {
|
|
261
|
-
const error = `Component '${component}' not found. Available: ${
|
|
261
|
+
const error = `Component '${component}' not found. Available: ${fragments.map(s => s.name).join(', ')}`;
|
|
262
262
|
if (isJsonOutput) {
|
|
263
263
|
console.log(JSON.stringify({ error }));
|
|
264
264
|
} else {
|
package/src/commands/add.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface AddOptions {
|
|
|
27
27
|
export interface AddResult {
|
|
28
28
|
success: boolean;
|
|
29
29
|
componentPath?: string;
|
|
30
|
-
|
|
30
|
+
fragmentPath: string;
|
|
31
31
|
indexPath?: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -110,7 +110,7 @@ export async function add(
|
|
|
110
110
|
// Determine output paths
|
|
111
111
|
const componentDir = resolve(process.cwd(), dir, componentName);
|
|
112
112
|
const componentFile = join(componentDir, `${componentName}.tsx`);
|
|
113
|
-
const
|
|
113
|
+
const fragmentFile = join(componentDir, `${componentName}${BRAND.fileExtension}`);
|
|
114
114
|
const indexFile = join(componentDir, 'index.ts');
|
|
115
115
|
|
|
116
116
|
// Check if directory already exists
|
|
@@ -133,10 +133,10 @@ export async function add(
|
|
|
133
133
|
console.log(`${pc.green('✓')} Created ${relative(process.cwd(), componentFile)}`);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
// Generate
|
|
137
|
-
const
|
|
138
|
-
await writeFile(
|
|
139
|
-
console.log(`${pc.green('✓')} Created ${relative(process.cwd(),
|
|
136
|
+
// Generate fragment file
|
|
137
|
+
const fragmentCode = generateFragmentStub(componentName, category, template);
|
|
138
|
+
await writeFile(fragmentFile, fragmentCode);
|
|
139
|
+
console.log(`${pc.green('✓')} Created ${relative(process.cwd(), fragmentFile)}`);
|
|
140
140
|
|
|
141
141
|
// Generate index.ts
|
|
142
142
|
const indexCode = `export { ${componentName} } from './${componentName}.js';\n`;
|
|
@@ -159,7 +159,7 @@ export async function add(
|
|
|
159
159
|
return {
|
|
160
160
|
success: true,
|
|
161
161
|
componentPath: generateComponent ? componentFile : undefined,
|
|
162
|
-
|
|
162
|
+
fragmentPath: fragmentFile,
|
|
163
163
|
indexPath: indexFile,
|
|
164
164
|
};
|
|
165
165
|
}
|
|
@@ -195,9 +195,9 @@ export function ${name}({ children, className }: ${name}Props) {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
|
-
* Generate a
|
|
198
|
+
* Generate a fragment stub based on template type.
|
|
199
199
|
*/
|
|
200
|
-
function
|
|
200
|
+
function generateFragmentStub(name: string, category: string, template: string): string {
|
|
201
201
|
// Template-specific usage hints
|
|
202
202
|
const usageHints: Record<string, { when: string[]; whenNot: string[] }> = {
|
|
203
203
|
action: {
|
|
@@ -231,10 +231,10 @@ function generateSegmentStub(name: string, category: string, template: string):
|
|
|
231
231
|
const scenarioTags = scenarioTagHints[template] || scenarioTagHints.display;
|
|
232
232
|
|
|
233
233
|
return `import React from 'react';
|
|
234
|
-
import {
|
|
234
|
+
import { defineFragment } from '@fragments/core';
|
|
235
235
|
import { ${name} } from './index.js';
|
|
236
236
|
|
|
237
|
-
export default
|
|
237
|
+
export default defineFragment({
|
|
238
238
|
component: ${name},
|
|
239
239
|
|
|
240
240
|
meta: {
|
package/src/commands/audit.ts
CHANGED
|
@@ -69,9 +69,9 @@ export async function audit(options: AuditOptions = {}): Promise<AuditSummary> {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// Fetch all fragments
|
|
72
|
-
const
|
|
72
|
+
const fragments = await client.getFragments();
|
|
73
73
|
|
|
74
|
-
if (
|
|
74
|
+
if (fragments.length === 0) {
|
|
75
75
|
if (json) {
|
|
76
76
|
console.log(JSON.stringify({ error: 'No fragments found', components: [] }));
|
|
77
77
|
} else {
|
|
@@ -91,11 +91,11 @@ export async function audit(options: AuditOptions = {}): Promise<AuditSummary> {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
if (!json) {
|
|
94
|
-
console.log(pc.dim(`Auditing ${
|
|
94
|
+
console.log(pc.dim(`Auditing ${fragments.length} component(s)...\n`));
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// Audit each fragment
|
|
98
|
-
for (const seg of
|
|
98
|
+
for (const seg of fragments) {
|
|
99
99
|
try {
|
|
100
100
|
// Get real compliance from the server
|
|
101
101
|
const complianceResult = await client.getCompliance({
|
package/src/commands/baseline.ts
CHANGED
|
@@ -98,16 +98,16 @@ async function updateBaseline(
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
const contextData = JSON.parse(await contextResp.text());
|
|
101
|
-
const
|
|
101
|
+
const fragments = contextData.components || [];
|
|
102
102
|
|
|
103
|
-
if (
|
|
103
|
+
if (fragments.length === 0) {
|
|
104
104
|
console.log(pc.yellow('No components found.\n'));
|
|
105
105
|
return { success: true, action: 'update', count: 0 };
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
component = await select({
|
|
109
109
|
message: 'Select component to update:',
|
|
110
|
-
choices:
|
|
110
|
+
choices: fragments.map((s: { name: string }) => ({
|
|
111
111
|
name: s.name,
|
|
112
112
|
value: s.name,
|
|
113
113
|
})),
|
package/src/commands/build.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { BRAND } from '../core/index.js';
|
|
7
7
|
import { loadConfig } from '../core/node.js';
|
|
8
|
-
import {
|
|
8
|
+
import { buildFragments, buildFragmentsDir } from '../build.js';
|
|
9
9
|
import { scan } from './scan.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -35,7 +35,7 @@ export interface BuildOptions {
|
|
|
35
35
|
*/
|
|
36
36
|
export interface BuildResult {
|
|
37
37
|
success: boolean;
|
|
38
|
-
|
|
38
|
+
fragmentCount?: number;
|
|
39
39
|
outputPath?: string;
|
|
40
40
|
componentCount?: number;
|
|
41
41
|
registryPath?: string;
|
|
@@ -65,7 +65,7 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
|
|
|
65
65
|
|
|
66
66
|
return {
|
|
67
67
|
success: scanResult.success,
|
|
68
|
-
|
|
68
|
+
fragmentCount: scanResult.componentCount,
|
|
69
69
|
outputPath: scanResult.outputPath,
|
|
70
70
|
errors: scanResult.errors.map((e) => ({ file: e.component, error: e.error })),
|
|
71
71
|
};
|
|
@@ -80,7 +80,7 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
|
|
|
80
80
|
console.log(pc.cyan(`\n${BRAND.name} Build\n`));
|
|
81
81
|
|
|
82
82
|
const errors: Array<{ file: string; error: string }> = [];
|
|
83
|
-
let
|
|
83
|
+
let fragmentCount: number | undefined;
|
|
84
84
|
let outputPath: string | undefined;
|
|
85
85
|
let componentCount: number | undefined;
|
|
86
86
|
let registryPath: string | undefined;
|
|
@@ -90,7 +90,7 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
|
|
|
90
90
|
if (!options.registryOnly) {
|
|
91
91
|
console.log(pc.dim('Compiling fragments...\n'));
|
|
92
92
|
|
|
93
|
-
const result = await
|
|
93
|
+
const result = await buildFragments(config, configDir);
|
|
94
94
|
|
|
95
95
|
if (result.errors.length > 0) {
|
|
96
96
|
console.log(pc.yellow('Build completed with errors:\n'));
|
|
@@ -109,10 +109,10 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
|
|
|
109
109
|
console.log();
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
fragmentCount = result.fragmentCount;
|
|
113
113
|
outputPath = result.outputPath;
|
|
114
114
|
|
|
115
|
-
console.log(pc.green(`✓ Built ${result.
|
|
115
|
+
console.log(pc.green(`✓ Built ${result.fragmentCount} fragment(s)`));
|
|
116
116
|
console.log(pc.dim(` Output: ${result.outputPath}\n`));
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -150,7 +150,7 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
|
|
|
150
150
|
|
|
151
151
|
return {
|
|
152
152
|
success: errors.length === 0,
|
|
153
|
-
|
|
153
|
+
fragmentCount,
|
|
154
154
|
outputPath,
|
|
155
155
|
componentCount,
|
|
156
156
|
registryPath,
|
package/src/commands/compare.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface CompareOptions {
|
|
|
15
15
|
config?: string;
|
|
16
16
|
/** Compare specific variant */
|
|
17
17
|
variant?: string;
|
|
18
|
-
/** Figma frame URL (uses
|
|
18
|
+
/** Figma frame URL (uses fragment figma link if not provided) */
|
|
19
19
|
figma?: string;
|
|
20
20
|
/** Diff threshold percentage */
|
|
21
21
|
threshold?: number;
|
|
@@ -112,7 +112,7 @@ export async function compare(
|
|
|
112
112
|
let failed = 0;
|
|
113
113
|
|
|
114
114
|
for (const comp of componentsToCompare) {
|
|
115
|
-
const response = await fetch(`${baseUrl}/
|
|
115
|
+
const response = await fetch(`${baseUrl}/fragments/compare`, {
|
|
116
116
|
method: 'POST',
|
|
117
117
|
headers: { 'Content-Type': 'application/json' },
|
|
118
118
|
body: JSON.stringify({
|
|
@@ -177,24 +177,24 @@ async function compareAll(
|
|
|
177
177
|
): Promise<CompareCommandResult> {
|
|
178
178
|
console.log(pc.dim('Comparing all components with Figma links...\n'));
|
|
179
179
|
|
|
180
|
-
// Fetch the context to get all
|
|
181
|
-
const contextResp = await fetch(`${baseUrl}/
|
|
180
|
+
// Fetch the context to get all fragments
|
|
181
|
+
const contextResp = await fetch(`${baseUrl}/fragments/context?format=json`);
|
|
182
182
|
if (!contextResp.ok) {
|
|
183
|
-
throw new Error('Failed to fetch
|
|
183
|
+
throw new Error('Failed to fetch fragments. Make sure dev server is running.');
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
const contextText = await contextResp.text();
|
|
187
|
-
let
|
|
187
|
+
let fragments: Array<{ name: string; figma?: string }> = [];
|
|
188
188
|
try {
|
|
189
189
|
const contextData = JSON.parse(contextText);
|
|
190
|
-
|
|
190
|
+
fragments = contextData.components || [];
|
|
191
191
|
} catch {
|
|
192
|
-
|
|
192
|
+
fragments = [];
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
if (
|
|
195
|
+
if (fragments.length === 0) {
|
|
196
196
|
console.log(pc.yellow('No components found with Figma links.'));
|
|
197
|
-
console.log(pc.dim('Add figma field to your
|
|
197
|
+
console.log(pc.dim('Add figma field to your fragment definitions:'));
|
|
198
198
|
console.log(pc.dim(' meta: { figma: "https://figma.com/file/..." }'));
|
|
199
199
|
return { success: true, passed: 0, failed: 0, skipped: 0 };
|
|
200
200
|
}
|
|
@@ -203,7 +203,7 @@ async function compareAll(
|
|
|
203
203
|
let failed = 0;
|
|
204
204
|
let skipped = 0;
|
|
205
205
|
|
|
206
|
-
for (const seg of
|
|
206
|
+
for (const seg of fragments) {
|
|
207
207
|
// Skip components without figma links
|
|
208
208
|
if (!seg.figma) {
|
|
209
209
|
skipped++;
|
|
@@ -212,7 +212,7 @@ async function compareAll(
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
try {
|
|
215
|
-
const response = await fetch(`${baseUrl}/
|
|
215
|
+
const response = await fetch(`${baseUrl}/fragments/compare`, {
|
|
216
216
|
method: 'POST',
|
|
217
217
|
headers: { 'Content-Type': 'application/json' },
|
|
218
218
|
body: JSON.stringify({
|
|
@@ -267,23 +267,23 @@ async function compareAll(
|
|
|
267
267
|
async function selectComponents(baseUrl: string): Promise<string[]> {
|
|
268
268
|
console.log(pc.dim('Fetching components with Figma links...\n'));
|
|
269
269
|
|
|
270
|
-
const contextResp = await fetch(`${baseUrl}/
|
|
270
|
+
const contextResp = await fetch(`${baseUrl}/fragments/context?format=json`);
|
|
271
271
|
if (!contextResp.ok) {
|
|
272
|
-
throw new Error('Failed to fetch
|
|
272
|
+
throw new Error('Failed to fetch fragments. Make sure dev server is running.');
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
const contextText = await contextResp.text();
|
|
276
|
-
let
|
|
276
|
+
let fragments: Array<{ name: string; figma?: string }> = [];
|
|
277
277
|
try {
|
|
278
278
|
const contextData = JSON.parse(contextText);
|
|
279
|
-
|
|
279
|
+
fragments = (contextData.components || []).filter((s: { figma?: string }) => s.figma);
|
|
280
280
|
} catch {
|
|
281
|
-
|
|
281
|
+
fragments = [];
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
if (
|
|
284
|
+
if (fragments.length === 0) {
|
|
285
285
|
console.log(pc.yellow('No components found with Figma links.'));
|
|
286
|
-
console.log(pc.dim('Add figma field to your
|
|
286
|
+
console.log(pc.dim('Add figma field to your fragment definitions:'));
|
|
287
287
|
console.log(pc.dim(' meta: { figma: "https://figma.com/file/..." }'));
|
|
288
288
|
return [];
|
|
289
289
|
}
|
|
@@ -291,7 +291,7 @@ async function selectComponents(baseUrl: string): Promise<string[]> {
|
|
|
291
291
|
const { checkbox } = await import('@inquirer/prompts');
|
|
292
292
|
|
|
293
293
|
try {
|
|
294
|
-
const choices =
|
|
294
|
+
const choices = fragments.map((seg) => ({
|
|
295
295
|
name: seg.name,
|
|
296
296
|
value: seg.name,
|
|
297
297
|
checked: true,
|
package/src/commands/context.ts
CHANGED
|
@@ -6,9 +6,9 @@ import { readFile } from 'node:fs/promises';
|
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
7
|
import pc from 'picocolors';
|
|
8
8
|
import { generateContext } from '../core/index.js';
|
|
9
|
-
import type {
|
|
9
|
+
import type { CompiledFragment, CompiledFragmentsFile } from '../core/index.js';
|
|
10
10
|
import { loadConfig } from '../core/node.js';
|
|
11
|
-
import {
|
|
11
|
+
import { buildFragments } from '../build.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Options for context command
|
|
@@ -16,7 +16,7 @@ import { buildSegments } from '../build.js';
|
|
|
16
16
|
export interface ContextOptions {
|
|
17
17
|
/** Path to config file */
|
|
18
18
|
config?: string;
|
|
19
|
-
/** Path to
|
|
19
|
+
/** Path to fragments.json (builds if not provided) */
|
|
20
20
|
input?: string;
|
|
21
21
|
/** Output format (markdown/json) */
|
|
22
22
|
format?: 'markdown' | 'json';
|
|
@@ -51,36 +51,36 @@ export async function context(options: ContextOptions = {}): Promise<ContextResu
|
|
|
51
51
|
tokensOnly = false,
|
|
52
52
|
} = options;
|
|
53
53
|
|
|
54
|
-
let
|
|
54
|
+
let fragments: CompiledFragment[];
|
|
55
55
|
|
|
56
56
|
if (options.input) {
|
|
57
|
-
// Read from existing
|
|
57
|
+
// Read from existing fragments.json
|
|
58
58
|
const inputPath = resolve(process.cwd(), options.input);
|
|
59
59
|
const content = await readFile(inputPath, 'utf-8');
|
|
60
|
-
const data:
|
|
61
|
-
|
|
60
|
+
const data: CompiledFragmentsFile = JSON.parse(content);
|
|
61
|
+
fragments = Object.values(data.fragments);
|
|
62
62
|
} else {
|
|
63
|
-
// Build
|
|
63
|
+
// Build fragments on the fly
|
|
64
64
|
const { config, configDir } = await loadConfig(options.config);
|
|
65
|
-
const result = await
|
|
65
|
+
const result = await buildFragments(config, configDir);
|
|
66
66
|
|
|
67
|
-
if (result.errors.length > 0 && result.
|
|
68
|
-
console.error(pc.red('Error: No
|
|
67
|
+
if (result.errors.length > 0 && result.fragmentCount === 0) {
|
|
68
|
+
console.error(pc.red('Error: No fragments found. Run `fragments build` first or fix errors.'));
|
|
69
69
|
return { success: false, tokenEstimate: 0 };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// Read the generated file
|
|
73
73
|
const content = await readFile(result.outputPath, 'utf-8');
|
|
74
|
-
const data:
|
|
75
|
-
|
|
74
|
+
const data: CompiledFragmentsFile = JSON.parse(content);
|
|
75
|
+
fragments = Object.values(data.fragments);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
if (
|
|
79
|
-
console.error(pc.red('No
|
|
78
|
+
if (fragments.length === 0) {
|
|
79
|
+
console.error(pc.red('No fragments found.'));
|
|
80
80
|
return { success: false, tokenEstimate: 0 };
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
const { content, tokenEstimate } = generateContext(
|
|
83
|
+
const { content, tokenEstimate } = generateContext(fragments, {
|
|
84
84
|
format,
|
|
85
85
|
compact,
|
|
86
86
|
include: {
|