@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
|
@@ -8,57 +8,57 @@ import {
|
|
|
8
8
|
formatMs,
|
|
9
9
|
generateHtmlReport,
|
|
10
10
|
getGrade
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-GKX2HPZ6.js";
|
|
12
12
|
import {
|
|
13
13
|
discoverBlockFiles,
|
|
14
14
|
discoverComponentFiles,
|
|
15
|
-
|
|
15
|
+
discoverFragmentFiles,
|
|
16
16
|
discoverTokenFiles,
|
|
17
17
|
extractComponentName,
|
|
18
18
|
generateContextMd,
|
|
19
19
|
generateRegistry,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from "./chunk-
|
|
20
|
+
loadFragmentFile,
|
|
21
|
+
parseFragmentFile
|
|
22
|
+
} from "./chunk-5ITIP3ES.js";
|
|
23
23
|
import {
|
|
24
24
|
compileBlock,
|
|
25
25
|
parseTokenFile
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-GCZMFLDI.js";
|
|
27
27
|
import {
|
|
28
28
|
BRAND,
|
|
29
29
|
DEFAULTS,
|
|
30
|
-
|
|
31
|
-
} from "./chunk-
|
|
30
|
+
fragmentDefinitionSchema
|
|
31
|
+
} from "./chunk-GHYYFAQN.js";
|
|
32
32
|
|
|
33
33
|
// src/validators.ts
|
|
34
34
|
async function validateSchema(config, configDir) {
|
|
35
|
-
const files = await
|
|
35
|
+
const files = await discoverFragmentFiles(config, configDir);
|
|
36
36
|
const errors = [];
|
|
37
37
|
const warnings = [];
|
|
38
38
|
for (const file of files) {
|
|
39
39
|
try {
|
|
40
|
-
const
|
|
41
|
-
if (!
|
|
40
|
+
const fragment = await loadFragmentFile(file.absolutePath);
|
|
41
|
+
if (!fragment) {
|
|
42
42
|
errors.push({
|
|
43
43
|
file: file.relativePath,
|
|
44
44
|
message: "No default export found",
|
|
45
|
-
details: `
|
|
45
|
+
details: `Fragment files must have a default export from defineFragment()`
|
|
46
46
|
});
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
|
-
const result =
|
|
49
|
+
const result = fragmentDefinitionSchema.safeParse(fragment);
|
|
50
50
|
if (!result.success) {
|
|
51
51
|
const details = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
|
|
52
52
|
errors.push({
|
|
53
53
|
file: file.relativePath,
|
|
54
|
-
message: "Invalid
|
|
54
|
+
message: "Invalid fragment schema",
|
|
55
55
|
details
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
} catch (error) {
|
|
59
59
|
errors.push({
|
|
60
60
|
file: file.relativePath,
|
|
61
|
-
message: "Failed to load
|
|
61
|
+
message: "Failed to load fragment file",
|
|
62
62
|
details: error instanceof Error ? error.message : String(error)
|
|
63
63
|
});
|
|
64
64
|
}
|
|
@@ -70,33 +70,33 @@ async function validateSchema(config, configDir) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
async function validateCoverage(config, configDir) {
|
|
73
|
-
const
|
|
73
|
+
const fragmentFiles = await discoverFragmentFiles(config, configDir);
|
|
74
74
|
const componentFiles = await discoverComponentFiles(config, configDir);
|
|
75
75
|
const errors = [];
|
|
76
76
|
const warnings = [];
|
|
77
77
|
const documentedComponents = /* @__PURE__ */ new Set();
|
|
78
|
-
for (const file of
|
|
78
|
+
for (const file of fragmentFiles) {
|
|
79
79
|
try {
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
documentedComponents.add(
|
|
80
|
+
const fragment = await loadFragmentFile(file.absolutePath);
|
|
81
|
+
if (fragment?.meta?.name) {
|
|
82
|
+
documentedComponents.add(fragment.meta.name);
|
|
83
83
|
}
|
|
84
84
|
} catch {
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
for (const file of componentFiles) {
|
|
88
88
|
const componentName = extractComponentName(file.relativePath);
|
|
89
|
-
const
|
|
89
|
+
const fragmentPath = file.relativePath.replace(
|
|
90
90
|
/\.(tsx?|jsx?)$/,
|
|
91
91
|
BRAND.fileExtension
|
|
92
92
|
);
|
|
93
|
-
const
|
|
94
|
-
(s) => s.relativePath ===
|
|
93
|
+
const hasFragmentFile = fragmentFiles.some(
|
|
94
|
+
(s) => s.relativePath === fragmentPath
|
|
95
95
|
);
|
|
96
|
-
if (!
|
|
96
|
+
if (!hasFragmentFile && !documentedComponents.has(componentName)) {
|
|
97
97
|
warnings.push({
|
|
98
98
|
file: file.relativePath,
|
|
99
|
-
message: `Component "${componentName}" has no
|
|
99
|
+
message: `Component "${componentName}" has no fragment documentation`
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -120,8 +120,8 @@ async function validateAll(config, configDir) {
|
|
|
120
120
|
|
|
121
121
|
// src/build.ts
|
|
122
122
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
123
|
-
import { resolve as
|
|
124
|
-
import { existsSync as
|
|
123
|
+
import { resolve as resolve3, join as join3 } from "path";
|
|
124
|
+
import { existsSync as existsSync3 } from "fs";
|
|
125
125
|
|
|
126
126
|
// src/core/auto-props.ts
|
|
127
127
|
import { existsSync, statSync } from "fs";
|
|
@@ -162,11 +162,11 @@ function resolveModulePath(basePath) {
|
|
|
162
162
|
}
|
|
163
163
|
return null;
|
|
164
164
|
}
|
|
165
|
-
function resolveComponentSourcePath(
|
|
165
|
+
function resolveComponentSourcePath(fragmentFileAbsolutePath, componentImportPath) {
|
|
166
166
|
if (!componentImportPath) return null;
|
|
167
167
|
if (!componentImportPath.startsWith(".")) return null;
|
|
168
|
-
const
|
|
169
|
-
const basePath = resolve(
|
|
168
|
+
const fragmentDir = dirname(fragmentFileAbsolutePath);
|
|
169
|
+
const basePath = resolve(fragmentDir, componentImportPath);
|
|
170
170
|
return resolveModulePath(basePath);
|
|
171
171
|
}
|
|
172
172
|
function collectTopLevelDeclarations(sourceFile) {
|
|
@@ -429,7 +429,391 @@ function extractCustomPropsFromComponentFile(componentFilePath, exportName) {
|
|
|
429
429
|
};
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
// src/core/graph-extractor.ts
|
|
433
|
+
import ts2 from "typescript";
|
|
434
|
+
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
435
|
+
import { join as join2 } from "path";
|
|
436
|
+
import { readdirSync } from "fs";
|
|
437
|
+
import { EDGE_TYPE_WEIGHTS, computeHealthFromData } from "@fragments-sdk/context/graph";
|
|
438
|
+
async function buildComponentGraph(fragments, blocks, componentDir, options) {
|
|
439
|
+
const knownComponents = new Set(Object.keys(fragments));
|
|
440
|
+
const allEdges = [];
|
|
441
|
+
const autoDetected = /* @__PURE__ */ new Map();
|
|
442
|
+
const warnings = [];
|
|
443
|
+
if (!options?.skipSourceAnalysis) {
|
|
444
|
+
const sourceEdges = extractImportAndHookEdges(componentDir, knownComponents);
|
|
445
|
+
allEdges.push(...sourceEdges);
|
|
446
|
+
const subComponentResults = extractSubComponents(componentDir, knownComponents);
|
|
447
|
+
for (const [name, subs] of subComponentResults) {
|
|
448
|
+
autoDetected.set(name, {
|
|
449
|
+
...autoDetected.get(name),
|
|
450
|
+
subComponents: subs,
|
|
451
|
+
compositionPattern: subs.length > 0 ? "compound" : "simple"
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const jsxEdges = extractJsxUsageEdges(fragments, knownComponents);
|
|
456
|
+
allEdges.push(...jsxEdges);
|
|
457
|
+
const blockEdges = extractBlockEdges(blocks);
|
|
458
|
+
allEdges.push(...blockEdges);
|
|
459
|
+
const relationEdges = extractRelationEdges(fragments);
|
|
460
|
+
allEdges.push(...relationEdges);
|
|
461
|
+
const requiredChildrenMap = inferRequiredChildren(fragments, autoDetected);
|
|
462
|
+
for (const [name, children] of requiredChildrenMap) {
|
|
463
|
+
const existing = autoDetected.get(name) ?? {};
|
|
464
|
+
autoDetected.set(name, { ...existing, requiredChildren: children });
|
|
465
|
+
}
|
|
466
|
+
const patternsMap = generateCommonPatterns(fragments, autoDetected);
|
|
467
|
+
for (const [name, patterns] of patternsMap) {
|
|
468
|
+
const existing = autoDetected.get(name) ?? {};
|
|
469
|
+
autoDetected.set(name, { ...existing, commonPatterns: patterns });
|
|
470
|
+
}
|
|
471
|
+
const mergedEdges = mergeAndDeduplicate(allEdges);
|
|
472
|
+
const nodes = Object.entries(fragments).map(([name, fragment]) => {
|
|
473
|
+
const detected = autoDetected.get(name);
|
|
474
|
+
return {
|
|
475
|
+
name,
|
|
476
|
+
category: fragment.meta.category,
|
|
477
|
+
status: fragment.meta.status ?? "stable",
|
|
478
|
+
compositionPattern: fragment.ai?.compositionPattern ?? detected?.compositionPattern,
|
|
479
|
+
subComponents: fragment.ai?.subComponents ?? detected?.subComponents
|
|
480
|
+
};
|
|
481
|
+
});
|
|
482
|
+
const blockIndex = /* @__PURE__ */ new Map();
|
|
483
|
+
for (const [blockName, block] of Object.entries(blocks)) {
|
|
484
|
+
for (const comp of block.components) {
|
|
485
|
+
const existing = blockIndex.get(comp);
|
|
486
|
+
if (existing) existing.push(blockName);
|
|
487
|
+
else blockIndex.set(comp, [blockName]);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const health = computeHealthFromData(nodes, mergedEdges, blockIndex);
|
|
491
|
+
for (const [name, fragment] of Object.entries(fragments)) {
|
|
492
|
+
const detected = autoDetected.get(name);
|
|
493
|
+
if (!detected) continue;
|
|
494
|
+
if (fragment.ai?.subComponents && detected.subComponents) {
|
|
495
|
+
const declared = new Set(fragment.ai.subComponents);
|
|
496
|
+
const found = new Set(detected.subComponents);
|
|
497
|
+
const missing = detected.subComponents.filter((s) => !declared.has(s));
|
|
498
|
+
const extra = fragment.ai.subComponents.filter((s) => !found.has(s));
|
|
499
|
+
if (missing.length > 0) {
|
|
500
|
+
warnings.push(
|
|
501
|
+
`${name}: declares ${declared.size} subComponents but code has ${found.size}. Missing from declaration: ${missing.join(", ")}`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
if (extra.length > 0) {
|
|
505
|
+
warnings.push(
|
|
506
|
+
`${name}: declares subComponents [${extra.join(", ")}] not found in Object.assign`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
graph: { nodes, edges: mergedEdges, health },
|
|
513
|
+
autoDetected,
|
|
514
|
+
warnings
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function extractImportAndHookEdges(componentDir, knownComponents) {
|
|
518
|
+
const edges = [];
|
|
519
|
+
for (const componentName of knownComponents) {
|
|
520
|
+
const indexPath = findComponentIndex(componentDir, componentName);
|
|
521
|
+
if (!indexPath) continue;
|
|
522
|
+
let sourceText;
|
|
523
|
+
try {
|
|
524
|
+
sourceText = readFileSync(indexPath, "utf-8");
|
|
525
|
+
} catch {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const sourceFile = ts2.createSourceFile(
|
|
529
|
+
indexPath,
|
|
530
|
+
sourceText,
|
|
531
|
+
ts2.ScriptTarget.Latest,
|
|
532
|
+
true,
|
|
533
|
+
indexPath.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS
|
|
534
|
+
);
|
|
535
|
+
const visitNode = (node) => {
|
|
536
|
+
if (ts2.isImportDeclaration(node)) {
|
|
537
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
538
|
+
if (ts2.isStringLiteral(moduleSpecifier)) {
|
|
539
|
+
const importPath = moduleSpecifier.text;
|
|
540
|
+
if (importPath.startsWith(".") || importPath.startsWith("/")) {
|
|
541
|
+
const clause = node.importClause;
|
|
542
|
+
if (clause) {
|
|
543
|
+
if (clause.name && isPascalCase(clause.name.text) && knownComponents.has(clause.name.text)) {
|
|
544
|
+
edges.push({
|
|
545
|
+
source: componentName,
|
|
546
|
+
target: clause.name.text,
|
|
547
|
+
type: "imports",
|
|
548
|
+
weight: EDGE_TYPE_WEIGHTS["imports"],
|
|
549
|
+
provenance: `source:${componentName}/index.tsx`
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (clause.namedBindings && ts2.isNamedImports(clause.namedBindings)) {
|
|
553
|
+
for (const element of clause.namedBindings.elements) {
|
|
554
|
+
const name = element.name.text;
|
|
555
|
+
if (isPascalCase(name) && knownComponents.has(name) && name !== componentName) {
|
|
556
|
+
edges.push({
|
|
557
|
+
source: componentName,
|
|
558
|
+
target: name,
|
|
559
|
+
type: "imports",
|
|
560
|
+
weight: EDGE_TYPE_WEIGHTS["imports"],
|
|
561
|
+
provenance: `source:${componentName}/index.tsx`
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (ts2.isCallExpression(node) && ts2.isIdentifier(node.expression)) {
|
|
571
|
+
const callName = node.expression.text;
|
|
572
|
+
const hookMatch = callName.match(/^use([A-Z][a-zA-Z]*)$/);
|
|
573
|
+
if (hookMatch) {
|
|
574
|
+
const hookTarget = hookMatch[1];
|
|
575
|
+
if (knownComponents.has(hookTarget) && hookTarget !== componentName) {
|
|
576
|
+
edges.push({
|
|
577
|
+
source: componentName,
|
|
578
|
+
target: hookTarget,
|
|
579
|
+
type: "hook-depends",
|
|
580
|
+
weight: EDGE_TYPE_WEIGHTS["hook-depends"],
|
|
581
|
+
provenance: `source:${componentName}/index.tsx`
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
ts2.forEachChild(node, visitNode);
|
|
587
|
+
};
|
|
588
|
+
ts2.forEachChild(sourceFile, visitNode);
|
|
589
|
+
}
|
|
590
|
+
return edges;
|
|
591
|
+
}
|
|
592
|
+
function extractSubComponents(componentDir, knownComponents) {
|
|
593
|
+
const result = /* @__PURE__ */ new Map();
|
|
594
|
+
for (const componentName of knownComponents) {
|
|
595
|
+
const indexPath = findComponentIndex(componentDir, componentName);
|
|
596
|
+
if (!indexPath) continue;
|
|
597
|
+
let sourceText;
|
|
598
|
+
try {
|
|
599
|
+
sourceText = readFileSync(indexPath, "utf-8");
|
|
600
|
+
} catch {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
if (!sourceText.includes("Object.assign")) continue;
|
|
604
|
+
const sourceFile = ts2.createSourceFile(
|
|
605
|
+
indexPath,
|
|
606
|
+
sourceText,
|
|
607
|
+
ts2.ScriptTarget.Latest,
|
|
608
|
+
true,
|
|
609
|
+
indexPath.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS
|
|
610
|
+
);
|
|
611
|
+
const subComponents = [];
|
|
612
|
+
const visitNode = (node) => {
|
|
613
|
+
if (ts2.isCallExpression(node) && ts2.isPropertyAccessExpression(node.expression) && ts2.isIdentifier(node.expression.expression) && node.expression.expression.text === "Object" && node.expression.name.text === "assign" && node.arguments.length >= 2) {
|
|
614
|
+
const propsArg = node.arguments[1];
|
|
615
|
+
if (ts2.isObjectLiteralExpression(propsArg)) {
|
|
616
|
+
for (const prop of propsArg.properties) {
|
|
617
|
+
if (ts2.isShorthandPropertyAssignment(prop)) {
|
|
618
|
+
subComponents.push(prop.name.text);
|
|
619
|
+
} else if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
|
|
620
|
+
subComponents.push(prop.name.text);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
ts2.forEachChild(node, visitNode);
|
|
626
|
+
};
|
|
627
|
+
ts2.forEachChild(sourceFile, visitNode);
|
|
628
|
+
if (subComponents.length > 0) {
|
|
629
|
+
result.set(componentName, subComponents);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return result;
|
|
633
|
+
}
|
|
634
|
+
function extractJsxUsageEdges(fragments, knownComponents) {
|
|
635
|
+
const edges = [];
|
|
636
|
+
const jsxTagRegex = /<([A-Z][a-zA-Z]*(?:\.[A-Z][a-zA-Z]*)?)/g;
|
|
637
|
+
for (const [name, fragment] of Object.entries(fragments)) {
|
|
638
|
+
const usedComponents = /* @__PURE__ */ new Set();
|
|
639
|
+
for (const variant of fragment.variants) {
|
|
640
|
+
if (!variant.code) continue;
|
|
641
|
+
let match;
|
|
642
|
+
jsxTagRegex.lastIndex = 0;
|
|
643
|
+
while ((match = jsxTagRegex.exec(variant.code)) !== null) {
|
|
644
|
+
let tagName = match[1];
|
|
645
|
+
if (tagName.includes(".")) {
|
|
646
|
+
tagName = tagName.split(".")[0];
|
|
647
|
+
}
|
|
648
|
+
if (knownComponents.has(tagName) && tagName !== name) {
|
|
649
|
+
usedComponents.add(tagName);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
for (const target of usedComponents) {
|
|
654
|
+
edges.push({
|
|
655
|
+
source: name,
|
|
656
|
+
target,
|
|
657
|
+
type: "renders",
|
|
658
|
+
weight: EDGE_TYPE_WEIGHTS["renders"],
|
|
659
|
+
provenance: `variant:${name}`
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return edges;
|
|
664
|
+
}
|
|
665
|
+
function extractBlockEdges(blocks) {
|
|
666
|
+
const edges = [];
|
|
667
|
+
for (const [blockName, block] of Object.entries(blocks)) {
|
|
668
|
+
const components = block.components;
|
|
669
|
+
for (let i = 0; i < components.length; i++) {
|
|
670
|
+
for (let j = i + 1; j < components.length; j++) {
|
|
671
|
+
edges.push({
|
|
672
|
+
source: components[i],
|
|
673
|
+
target: components[j],
|
|
674
|
+
type: "composes",
|
|
675
|
+
weight: EDGE_TYPE_WEIGHTS["composes"],
|
|
676
|
+
provenance: `block:${blockName}`
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return edges;
|
|
682
|
+
}
|
|
683
|
+
function extractRelationEdges(fragments) {
|
|
684
|
+
const edges = [];
|
|
685
|
+
const relationToEdgeType = {
|
|
686
|
+
parent: "parent-of",
|
|
687
|
+
child: "parent-of",
|
|
688
|
+
// reversed: if A declares child B, edge is A parent-of B
|
|
689
|
+
composition: "composes",
|
|
690
|
+
alternative: "alternative-to",
|
|
691
|
+
sibling: "sibling-of"
|
|
692
|
+
};
|
|
693
|
+
for (const [name, fragment] of Object.entries(fragments)) {
|
|
694
|
+
if (!fragment.relations) continue;
|
|
695
|
+
for (const rel of fragment.relations) {
|
|
696
|
+
const edgeType = relationToEdgeType[rel.relationship];
|
|
697
|
+
if (!edgeType) continue;
|
|
698
|
+
let source;
|
|
699
|
+
let target;
|
|
700
|
+
if (rel.relationship === "parent") {
|
|
701
|
+
source = rel.component;
|
|
702
|
+
target = name;
|
|
703
|
+
} else {
|
|
704
|
+
source = name;
|
|
705
|
+
target = rel.component;
|
|
706
|
+
}
|
|
707
|
+
edges.push({
|
|
708
|
+
source,
|
|
709
|
+
target,
|
|
710
|
+
type: edgeType,
|
|
711
|
+
weight: EDGE_TYPE_WEIGHTS[edgeType],
|
|
712
|
+
note: rel.note,
|
|
713
|
+
provenance: "relation"
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return edges;
|
|
718
|
+
}
|
|
719
|
+
function inferRequiredChildren(fragments, autoDetected) {
|
|
720
|
+
const result = /* @__PURE__ */ new Map();
|
|
721
|
+
for (const [name, fragment] of Object.entries(fragments)) {
|
|
722
|
+
const detected = autoDetected.get(name);
|
|
723
|
+
const subs = detected?.subComponents ?? fragment.ai?.subComponents;
|
|
724
|
+
if (!subs || subs.length === 0) continue;
|
|
725
|
+
const variantsWithCode = fragment.variants.filter((v) => v.code);
|
|
726
|
+
if (variantsWithCode.length === 0) continue;
|
|
727
|
+
const required = [];
|
|
728
|
+
for (const sub of subs) {
|
|
729
|
+
const inAll = variantsWithCode.every((v) => {
|
|
730
|
+
const patterns = [
|
|
731
|
+
new RegExp(`<${name}\\.${sub}[\\s/>]`),
|
|
732
|
+
new RegExp(`<${sub}[\\s/>]`)
|
|
733
|
+
];
|
|
734
|
+
return patterns.some((p) => p.test(v.code));
|
|
735
|
+
});
|
|
736
|
+
if (inAll) required.push(sub);
|
|
737
|
+
}
|
|
738
|
+
if (required.length > 0) {
|
|
739
|
+
result.set(name, required);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return result;
|
|
743
|
+
}
|
|
744
|
+
function generateCommonPatterns(fragments, autoDetected) {
|
|
745
|
+
const result = /* @__PURE__ */ new Map();
|
|
746
|
+
for (const [name, fragment] of Object.entries(fragments)) {
|
|
747
|
+
const detected = autoDetected.get(name);
|
|
748
|
+
const subs = detected?.subComponents ?? fragment.ai?.subComponents;
|
|
749
|
+
if (!subs || subs.length === 0) continue;
|
|
750
|
+
const firstVariant = fragment.variants.find((v) => v.code);
|
|
751
|
+
if (!firstVariant?.code) continue;
|
|
752
|
+
const usedSubs = [];
|
|
753
|
+
for (const sub of subs) {
|
|
754
|
+
const patterns = [
|
|
755
|
+
new RegExp(`<${name}\\.${sub}`),
|
|
756
|
+
new RegExp(`<${sub}[\\s/>]`)
|
|
757
|
+
];
|
|
758
|
+
if (patterns.some((p) => p.test(firstVariant.code))) {
|
|
759
|
+
usedSubs.push(sub);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (usedSubs.length > 0) {
|
|
763
|
+
const pattern = `<${name}>
|
|
764
|
+
${usedSubs.map((s) => ` <${name}.${s}>...</${name}.${s}>`).join("\n")}
|
|
765
|
+
</${name}>`;
|
|
766
|
+
result.set(name, [pattern]);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return result;
|
|
770
|
+
}
|
|
771
|
+
function mergeAndDeduplicate(edges) {
|
|
772
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
773
|
+
for (const edge of edges) {
|
|
774
|
+
const key = `${edge.source}\u2192${edge.target}:${edge.type}`;
|
|
775
|
+
const existing = edgeMap.get(key);
|
|
776
|
+
if (!existing || edge.weight > existing.weight) {
|
|
777
|
+
edgeMap.set(key, edge);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return [...edgeMap.values()];
|
|
781
|
+
}
|
|
782
|
+
function isPascalCase(name) {
|
|
783
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
784
|
+
}
|
|
785
|
+
function findComponentIndex(componentDir, componentName) {
|
|
786
|
+
const candidates = [
|
|
787
|
+
join2(componentDir, componentName, "index.tsx"),
|
|
788
|
+
join2(componentDir, componentName, "index.ts"),
|
|
789
|
+
join2(componentDir, componentName, `${componentName}.tsx`),
|
|
790
|
+
join2(componentDir, componentName, `${componentName}.ts`)
|
|
791
|
+
];
|
|
792
|
+
for (const candidate of candidates) {
|
|
793
|
+
if (existsSync2(candidate)) {
|
|
794
|
+
return candidate;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
const entries = readdirSync(componentDir, { withFileTypes: true });
|
|
799
|
+
for (const entry of entries) {
|
|
800
|
+
if (entry.isDirectory() && entry.name === componentName) {
|
|
801
|
+
const subCandidates = [
|
|
802
|
+
join2(componentDir, entry.name, "index.tsx"),
|
|
803
|
+
join2(componentDir, entry.name, "index.ts")
|
|
804
|
+
];
|
|
805
|
+
for (const sc of subCandidates) {
|
|
806
|
+
if (existsSync2(sc)) return sc;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
|
|
432
815
|
// src/build.ts
|
|
816
|
+
import { serializeGraph } from "@fragments-sdk/context/graph";
|
|
433
817
|
function normalizeParsedProps(parsedProps) {
|
|
434
818
|
return Object.fromEntries(
|
|
435
819
|
Object.entries(parsedProps).map(([name, prop]) => [
|
|
@@ -464,15 +848,15 @@ function mergeDocumentedAndAutoProps(documentedProps, autoProps) {
|
|
|
464
848
|
})
|
|
465
849
|
);
|
|
466
850
|
}
|
|
467
|
-
async function
|
|
468
|
-
const files = await
|
|
851
|
+
async function buildFragments(config, configDir) {
|
|
852
|
+
const files = await discoverFragmentFiles(config, configDir);
|
|
469
853
|
const errors = [];
|
|
470
854
|
const warnings = [];
|
|
471
|
-
const
|
|
855
|
+
const fragments = {};
|
|
472
856
|
for (const file of files) {
|
|
473
857
|
try {
|
|
474
858
|
const content = await readFile(file.absolutePath, "utf-8");
|
|
475
|
-
const parsed =
|
|
859
|
+
const parsed = parseFragmentFile(content, file.relativePath);
|
|
476
860
|
for (const warning of parsed.warnings) {
|
|
477
861
|
warnings.push({ file: file.relativePath, warning });
|
|
478
862
|
}
|
|
@@ -563,7 +947,7 @@ async function buildSegments(config, configDir) {
|
|
|
563
947
|
// Include AI metadata if present
|
|
564
948
|
...parsed.ai && { ai: parsed.ai }
|
|
565
949
|
};
|
|
566
|
-
|
|
950
|
+
fragments[parsed.meta.name] = compiled;
|
|
567
951
|
} catch (error) {
|
|
568
952
|
errors.push({
|
|
569
953
|
file: file.relativePath,
|
|
@@ -576,7 +960,7 @@ async function buildSegments(config, configDir) {
|
|
|
576
960
|
const blockFiles = await discoverBlockFiles(configDir, config.exclude);
|
|
577
961
|
for (const file of blockFiles) {
|
|
578
962
|
try {
|
|
579
|
-
let raw = await
|
|
963
|
+
let raw = await loadFragmentFile(file.absolutePath);
|
|
580
964
|
if (raw && "default" in raw && typeof raw.default === "object") {
|
|
581
965
|
raw = raw.default;
|
|
582
966
|
}
|
|
@@ -625,35 +1009,67 @@ async function buildSegments(config, configDir) {
|
|
|
625
1009
|
} catch {
|
|
626
1010
|
}
|
|
627
1011
|
let packageName;
|
|
628
|
-
const pkgJsonPath =
|
|
629
|
-
if (
|
|
1012
|
+
const pkgJsonPath = resolve3(configDir, "package.json");
|
|
1013
|
+
if (existsSync3(pkgJsonPath)) {
|
|
630
1014
|
try {
|
|
631
1015
|
const pkg = JSON.parse(await readFile(pkgJsonPath, "utf-8"));
|
|
632
1016
|
if (pkg.name) packageName = pkg.name;
|
|
633
1017
|
} catch {
|
|
634
1018
|
}
|
|
635
1019
|
}
|
|
1020
|
+
const componentDir = resolve3(configDir, "src", "components");
|
|
1021
|
+
let graphData;
|
|
1022
|
+
try {
|
|
1023
|
+
const graphResult = await buildComponentGraph(fragments, blocks, componentDir);
|
|
1024
|
+
for (const [name, fragment] of Object.entries(fragments)) {
|
|
1025
|
+
const detected = graphResult.autoDetected.get(name);
|
|
1026
|
+
if (!detected) continue;
|
|
1027
|
+
if (!fragment.ai) fragment.ai = {};
|
|
1028
|
+
if (!fragment.ai.subComponents && detected.subComponents) {
|
|
1029
|
+
fragment.ai.subComponents = detected.subComponents;
|
|
1030
|
+
}
|
|
1031
|
+
if (!fragment.ai.compositionPattern && detected.compositionPattern) {
|
|
1032
|
+
fragment.ai.compositionPattern = detected.compositionPattern;
|
|
1033
|
+
}
|
|
1034
|
+
if (!fragment.ai.commonPatterns && detected.commonPatterns) {
|
|
1035
|
+
fragment.ai.commonPatterns = detected.commonPatterns;
|
|
1036
|
+
}
|
|
1037
|
+
if (!fragment.ai.requiredChildren && detected.requiredChildren) {
|
|
1038
|
+
fragment.ai.requiredChildren = detected.requiredChildren;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
for (const w of graphResult.warnings) {
|
|
1042
|
+
warnings.push({ file: "graph", warning: w });
|
|
1043
|
+
}
|
|
1044
|
+
graphData = serializeGraph(graphResult.graph);
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
warnings.push({
|
|
1047
|
+
file: "graph",
|
|
1048
|
+
warning: `Graph extraction failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
636
1051
|
const output = {
|
|
637
1052
|
version: "1.0.0",
|
|
638
1053
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
639
1054
|
...packageName && { packageName },
|
|
640
|
-
|
|
1055
|
+
fragments,
|
|
641
1056
|
...Object.keys(blocks).length > 0 && { blocks },
|
|
642
|
-
...tokens && { tokens }
|
|
1057
|
+
...tokens && { tokens },
|
|
1058
|
+
...graphData && { graph: graphData }
|
|
643
1059
|
};
|
|
644
|
-
const outputPath =
|
|
1060
|
+
const outputPath = resolve3(configDir, config.outFile ?? BRAND.outFile);
|
|
645
1061
|
await writeFile(outputPath, JSON.stringify(output));
|
|
646
1062
|
return {
|
|
647
1063
|
success: errors.length === 0,
|
|
648
1064
|
outputPath,
|
|
649
|
-
|
|
1065
|
+
fragmentCount: Object.keys(fragments).length,
|
|
650
1066
|
errors,
|
|
651
1067
|
warnings
|
|
652
1068
|
};
|
|
653
1069
|
}
|
|
654
1070
|
async function buildFragmentsDir(config, configDir) {
|
|
655
|
-
const fragmentsDir =
|
|
656
|
-
const componentsDir =
|
|
1071
|
+
const fragmentsDir = join3(configDir, BRAND.dataDir);
|
|
1072
|
+
const componentsDir = join3(fragmentsDir, BRAND.componentsDir);
|
|
657
1073
|
await mkdir(fragmentsDir, { recursive: true });
|
|
658
1074
|
await mkdir(componentsDir, { recursive: true });
|
|
659
1075
|
const registryResult = await generateRegistry({
|
|
@@ -665,9 +1081,9 @@ async function buildFragmentsDir(config, configDir) {
|
|
|
665
1081
|
});
|
|
666
1082
|
const errors = [...registryResult.errors];
|
|
667
1083
|
const warnings = [...registryResult.warnings];
|
|
668
|
-
const indexPath =
|
|
1084
|
+
const indexPath = join3(fragmentsDir, "index.json");
|
|
669
1085
|
await writeFile(indexPath, JSON.stringify(registryResult.index, null, 2));
|
|
670
|
-
const registryPath =
|
|
1086
|
+
const registryPath = join3(fragmentsDir, BRAND.registryFile);
|
|
671
1087
|
await writeFile(registryPath, JSON.stringify(registryResult.registry, null, 2));
|
|
672
1088
|
const contextResult = generateContextMd(registryResult.registry, {
|
|
673
1089
|
format: "markdown",
|
|
@@ -679,7 +1095,7 @@ async function buildFragmentsDir(config, configDir) {
|
|
|
679
1095
|
code: false
|
|
680
1096
|
}
|
|
681
1097
|
});
|
|
682
|
-
const contextPath =
|
|
1098
|
+
const contextPath = join3(fragmentsDir, BRAND.contextFile);
|
|
683
1099
|
await writeFile(contextPath, contextResult.content);
|
|
684
1100
|
return {
|
|
685
1101
|
success: errors.length === 0,
|
|
@@ -702,9 +1118,9 @@ async function runScreenshotCommand(config, configDir, options = {}) {
|
|
|
702
1118
|
viewport: options.width && options.height ? { width: options.width, height: options.height } : config.screenshots?.viewport
|
|
703
1119
|
});
|
|
704
1120
|
await storage.initialize();
|
|
705
|
-
const
|
|
706
|
-
if (
|
|
707
|
-
console.log(pc.yellow("No
|
|
1121
|
+
const fragmentFiles = await discoverFragmentFiles(config, configDir);
|
|
1122
|
+
if (fragmentFiles.length === 0) {
|
|
1123
|
+
console.log(pc.yellow("No fragment files found."));
|
|
708
1124
|
return {
|
|
709
1125
|
success: true,
|
|
710
1126
|
captured: 0,
|
|
@@ -713,12 +1129,12 @@ async function runScreenshotCommand(config, configDir, options = {}) {
|
|
|
713
1129
|
totalTimeMs: Date.now() - startTime
|
|
714
1130
|
};
|
|
715
1131
|
}
|
|
716
|
-
const
|
|
717
|
-
for (const file of
|
|
1132
|
+
const fragments = [];
|
|
1133
|
+
for (const file of fragmentFiles) {
|
|
718
1134
|
try {
|
|
719
|
-
const
|
|
720
|
-
if (
|
|
721
|
-
|
|
1135
|
+
const fragment = await loadFragmentFile(file.absolutePath);
|
|
1136
|
+
if (fragment) {
|
|
1137
|
+
fragments.push({ path: file.relativePath, fragment });
|
|
722
1138
|
}
|
|
723
1139
|
} catch (error) {
|
|
724
1140
|
errors.push({
|
|
@@ -728,8 +1144,8 @@ async function runScreenshotCommand(config, configDir, options = {}) {
|
|
|
728
1144
|
});
|
|
729
1145
|
}
|
|
730
1146
|
}
|
|
731
|
-
const
|
|
732
|
-
if (options.component &&
|
|
1147
|
+
const filteredFragments = options.component ? fragments.filter((s) => s.fragment.meta.name === options.component) : fragments;
|
|
1148
|
+
if (options.component && filteredFragments.length === 0) {
|
|
733
1149
|
console.log(pc.yellow(`Component "${options.component}" not found.`));
|
|
734
1150
|
return {
|
|
735
1151
|
success: false,
|
|
@@ -740,11 +1156,11 @@ async function runScreenshotCommand(config, configDir, options = {}) {
|
|
|
740
1156
|
};
|
|
741
1157
|
}
|
|
742
1158
|
const variantsToCapture = [];
|
|
743
|
-
for (const {
|
|
744
|
-
const variants = options.variant ?
|
|
1159
|
+
for (const { fragment } of filteredFragments) {
|
|
1160
|
+
const variants = options.variant ? fragment.variants.filter((v) => v.name === options.variant) : fragment.variants;
|
|
745
1161
|
for (const variant of variants) {
|
|
746
1162
|
variantsToCapture.push({
|
|
747
|
-
component:
|
|
1163
|
+
component: fragment.meta.name,
|
|
748
1164
|
variant: variant.name,
|
|
749
1165
|
render: variant.render
|
|
750
1166
|
});
|
|
@@ -848,9 +1264,9 @@ async function runDiffCommand(config, configDir, options = {}) {
|
|
|
848
1264
|
await storage.initialize();
|
|
849
1265
|
const threshold = options.threshold ?? config.screenshots?.threshold ?? DEFAULTS.diffThreshold;
|
|
850
1266
|
const diffEngine = new DiffEngine(threshold);
|
|
851
|
-
const
|
|
852
|
-
if (
|
|
853
|
-
console.log(pc2.yellow("No
|
|
1267
|
+
const fragmentFiles = await discoverFragmentFiles(config, configDir);
|
|
1268
|
+
if (fragmentFiles.length === 0) {
|
|
1269
|
+
console.log(pc2.yellow("No fragment files found."));
|
|
854
1270
|
return {
|
|
855
1271
|
success: true,
|
|
856
1272
|
total: 0,
|
|
@@ -861,18 +1277,18 @@ async function runDiffCommand(config, configDir, options = {}) {
|
|
|
861
1277
|
totalTimeMs: Date.now() - startTime
|
|
862
1278
|
};
|
|
863
1279
|
}
|
|
864
|
-
const
|
|
865
|
-
for (const file of
|
|
1280
|
+
const fragments = [];
|
|
1281
|
+
for (const file of fragmentFiles) {
|
|
866
1282
|
try {
|
|
867
|
-
const
|
|
868
|
-
if (
|
|
869
|
-
|
|
1283
|
+
const fragment = await loadFragmentFile(file.absolutePath);
|
|
1284
|
+
if (fragment) {
|
|
1285
|
+
fragments.push({ path: file.relativePath, fragment });
|
|
870
1286
|
}
|
|
871
1287
|
} catch {
|
|
872
1288
|
}
|
|
873
1289
|
}
|
|
874
|
-
const
|
|
875
|
-
if (options.component &&
|
|
1290
|
+
const filteredFragments = options.component ? fragments.filter((s) => s.fragment.meta.name === options.component) : fragments;
|
|
1291
|
+
if (options.component && filteredFragments.length === 0) {
|
|
876
1292
|
console.log(pc2.yellow(`Component "${options.component}" not found.`));
|
|
877
1293
|
return {
|
|
878
1294
|
success: false,
|
|
@@ -885,11 +1301,11 @@ async function runDiffCommand(config, configDir, options = {}) {
|
|
|
885
1301
|
};
|
|
886
1302
|
}
|
|
887
1303
|
const variantsToDiff = [];
|
|
888
|
-
for (const {
|
|
889
|
-
const variants = options.variant ?
|
|
1304
|
+
for (const { fragment } of filteredFragments) {
|
|
1305
|
+
const variants = options.variant ? fragment.variants.filter((v) => v.name === options.variant) : fragment.variants;
|
|
890
1306
|
for (const variant of variants) {
|
|
891
1307
|
variantsToDiff.push({
|
|
892
|
-
component:
|
|
1308
|
+
component: fragment.meta.name,
|
|
893
1309
|
variant: variant.name
|
|
894
1310
|
});
|
|
895
1311
|
}
|
|
@@ -1032,9 +1448,9 @@ ${BRAND.name} Diff
|
|
|
1032
1448
|
}
|
|
1033
1449
|
|
|
1034
1450
|
// src/analyze.ts
|
|
1035
|
-
import { existsSync as
|
|
1451
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1036
1452
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1037
|
-
import { join as
|
|
1453
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
1038
1454
|
import pc3 from "picocolors";
|
|
1039
1455
|
async function runAnalyzeCommand(config, configDir, options = {}) {
|
|
1040
1456
|
const format = options.format ?? "html";
|
|
@@ -1042,9 +1458,9 @@ async function runAnalyzeCommand(config, configDir, options = {}) {
|
|
|
1042
1458
|
console.log(pc3.cyan(`
|
|
1043
1459
|
${BRAND.name} Analyzer
|
|
1044
1460
|
`));
|
|
1045
|
-
const
|
|
1046
|
-
if (!
|
|
1047
|
-
console.log(pc3.red(`\u2717 No
|
|
1461
|
+
const fragmentsPath = join4(configDir, config.outFile ?? "fragments.json");
|
|
1462
|
+
if (!existsSync4(fragmentsPath)) {
|
|
1463
|
+
console.log(pc3.red(`\u2717 No fragments.json found. Run \`${BRAND.cliCommand} build\` first.
|
|
1048
1464
|
`));
|
|
1049
1465
|
return {
|
|
1050
1466
|
success: false,
|
|
@@ -1052,7 +1468,7 @@ ${BRAND.name} Analyzer
|
|
|
1052
1468
|
};
|
|
1053
1469
|
}
|
|
1054
1470
|
console.log(pc3.dim("Analyzing design system...\n"));
|
|
1055
|
-
const content = await readFile2(
|
|
1471
|
+
const content = await readFile2(fragmentsPath, "utf-8");
|
|
1056
1472
|
const data = JSON.parse(content);
|
|
1057
1473
|
const analytics = analyzeDesignSystem(data);
|
|
1058
1474
|
printConsoleSummary(analytics);
|
|
@@ -1137,8 +1553,8 @@ function colorizeScore(score) {
|
|
|
1137
1553
|
return pc3.red(`${score}%`);
|
|
1138
1554
|
}
|
|
1139
1555
|
function getDefaultOutputPath(format, configDir) {
|
|
1140
|
-
const filename = format === "html" ? "
|
|
1141
|
-
return
|
|
1556
|
+
const filename = format === "html" ? "fragments-report.html" : "fragments-report.json";
|
|
1557
|
+
return join4(configDir, filename);
|
|
1142
1558
|
}
|
|
1143
1559
|
async function openInBrowser(path) {
|
|
1144
1560
|
const { platform } = await import("os");
|
|
@@ -1200,10 +1616,10 @@ export {
|
|
|
1200
1616
|
validateSchema,
|
|
1201
1617
|
validateCoverage,
|
|
1202
1618
|
validateAll,
|
|
1203
|
-
|
|
1619
|
+
buildFragments,
|
|
1204
1620
|
buildFragmentsDir,
|
|
1205
1621
|
runScreenshotCommand,
|
|
1206
1622
|
runDiffCommand,
|
|
1207
1623
|
runAnalyzeCommand
|
|
1208
1624
|
};
|
|
1209
|
-
//# sourceMappingURL=chunk-
|
|
1625
|
+
//# sourceMappingURL=chunk-U6VTHBNI.js.map
|