@fragments-sdk/cli 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type { CompiledSegment, RelationshipType } from "./types.js";
|
|
2
|
+
|
|
3
|
+
// --- Public types ---
|
|
4
|
+
|
|
5
|
+
export interface CompositionWarning {
|
|
6
|
+
type:
|
|
7
|
+
| "missing_parent"
|
|
8
|
+
| "missing_child"
|
|
9
|
+
| "missing_composition"
|
|
10
|
+
| "redundant_alternative"
|
|
11
|
+
| "deprecated"
|
|
12
|
+
| "experimental";
|
|
13
|
+
component: string;
|
|
14
|
+
message: string;
|
|
15
|
+
relatedComponent?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CompositionSuggestion {
|
|
19
|
+
component: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
relationship: RelationshipType | "category_gap";
|
|
22
|
+
sourceComponent: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CompositionGuideline {
|
|
26
|
+
component: string;
|
|
27
|
+
guideline: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CompositionAnalysis {
|
|
31
|
+
/** The validated component names (filtered to those that exist) */
|
|
32
|
+
components: string[];
|
|
33
|
+
|
|
34
|
+
/** Components requested but not found in the registry */
|
|
35
|
+
unknown: string[];
|
|
36
|
+
|
|
37
|
+
/** Issues with the current selection */
|
|
38
|
+
warnings: CompositionWarning[];
|
|
39
|
+
|
|
40
|
+
/** Components to consider adding */
|
|
41
|
+
suggestions: CompositionSuggestion[];
|
|
42
|
+
|
|
43
|
+
/** Relevant usage guidelines for the selected components */
|
|
44
|
+
guidelines: CompositionGuideline[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Category affinities ---
|
|
48
|
+
|
|
49
|
+
const CATEGORY_AFFINITIES: Record<string, string[]> = {
|
|
50
|
+
forms: ["feedback"],
|
|
51
|
+
actions: ["feedback"],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// --- Main function ---
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Analyzes a set of components as a composition group.
|
|
58
|
+
* Returns warnings about missing relations, usage conflicts,
|
|
59
|
+
* and suggestions for additional components.
|
|
60
|
+
*
|
|
61
|
+
* Browser-safe: no Node.js APIs used.
|
|
62
|
+
*/
|
|
63
|
+
export function analyzeComposition(
|
|
64
|
+
segments: Record<string, CompiledSegment>,
|
|
65
|
+
componentNames: string[],
|
|
66
|
+
_context?: string
|
|
67
|
+
): CompositionAnalysis {
|
|
68
|
+
const allNames = new Set(Object.keys(segments));
|
|
69
|
+
|
|
70
|
+
// 1. Validate names
|
|
71
|
+
const components: string[] = [];
|
|
72
|
+
const unknown: string[] = [];
|
|
73
|
+
for (const name of componentNames) {
|
|
74
|
+
if (allNames.has(name)) {
|
|
75
|
+
components.push(name);
|
|
76
|
+
} else {
|
|
77
|
+
unknown.push(name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const selectedSet = new Set(components);
|
|
82
|
+
const warnings: CompositionWarning[] = [];
|
|
83
|
+
const suggestions: CompositionSuggestion[] = [];
|
|
84
|
+
const guidelines: CompositionGuideline[] = [];
|
|
85
|
+
|
|
86
|
+
// Track suggestions to avoid duplicates
|
|
87
|
+
const suggestedSet = new Set<string>();
|
|
88
|
+
|
|
89
|
+
for (const name of components) {
|
|
90
|
+
const segment = segments[name];
|
|
91
|
+
|
|
92
|
+
// 2. Relation checks
|
|
93
|
+
if (segment.relations) {
|
|
94
|
+
for (const rel of segment.relations) {
|
|
95
|
+
switch (rel.relationship) {
|
|
96
|
+
case "parent":
|
|
97
|
+
if (!selectedSet.has(rel.component)) {
|
|
98
|
+
warnings.push({
|
|
99
|
+
type: "missing_parent",
|
|
100
|
+
component: name,
|
|
101
|
+
message: `"${name}" expects to be wrapped by "${rel.component}"${rel.note ? `: ${rel.note}` : ""}`,
|
|
102
|
+
relatedComponent: rel.component,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case "child":
|
|
108
|
+
if (!selectedSet.has(rel.component) && !suggestedSet.has(rel.component)) {
|
|
109
|
+
suggestions.push({
|
|
110
|
+
component: rel.component,
|
|
111
|
+
reason: `"${name}" typically contains "${rel.component}"${rel.note ? `: ${rel.note}` : ""}`,
|
|
112
|
+
relationship: "child",
|
|
113
|
+
sourceComponent: name,
|
|
114
|
+
});
|
|
115
|
+
suggestedSet.add(rel.component);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case "composition":
|
|
120
|
+
if (!selectedSet.has(rel.component)) {
|
|
121
|
+
warnings.push({
|
|
122
|
+
type: "missing_composition",
|
|
123
|
+
component: name,
|
|
124
|
+
message: `"${name}" is typically used together with "${rel.component}"${rel.note ? `: ${rel.note}` : ""}`,
|
|
125
|
+
relatedComponent: rel.component,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case "sibling":
|
|
131
|
+
if (!selectedSet.has(rel.component) && !suggestedSet.has(rel.component)) {
|
|
132
|
+
suggestions.push({
|
|
133
|
+
component: rel.component,
|
|
134
|
+
reason: `"${rel.component}" is a sibling of "${name}"${rel.note ? `: ${rel.note}` : ""}`,
|
|
135
|
+
relationship: "sibling",
|
|
136
|
+
sourceComponent: name,
|
|
137
|
+
});
|
|
138
|
+
suggestedSet.add(rel.component);
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
|
|
142
|
+
case "alternative":
|
|
143
|
+
if (selectedSet.has(rel.component)) {
|
|
144
|
+
warnings.push({
|
|
145
|
+
type: "redundant_alternative",
|
|
146
|
+
component: name,
|
|
147
|
+
message: `"${name}" and "${rel.component}" are alternatives — using both may be redundant${rel.note ? `: ${rel.note}` : ""}`,
|
|
148
|
+
relatedComponent: rel.component,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 3. Usage conflict checks (whenNot)
|
|
157
|
+
if (segment.usage?.whenNot) {
|
|
158
|
+
for (const whenNotEntry of segment.usage.whenNot) {
|
|
159
|
+
const lower = whenNotEntry.toLowerCase();
|
|
160
|
+
for (const other of components) {
|
|
161
|
+
if (other !== name && lower.includes(other.toLowerCase())) {
|
|
162
|
+
guidelines.push({
|
|
163
|
+
component: name,
|
|
164
|
+
guideline: `Potential conflict with "${other}": ${whenNotEntry}`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 4. Status warnings
|
|
172
|
+
if (segment.meta.status === "deprecated") {
|
|
173
|
+
warnings.push({
|
|
174
|
+
type: "deprecated",
|
|
175
|
+
component: name,
|
|
176
|
+
message: segment.meta.description
|
|
177
|
+
? `"${name}" is deprecated: ${segment.meta.description}`
|
|
178
|
+
: `"${name}" is deprecated`,
|
|
179
|
+
});
|
|
180
|
+
} else if (segment.meta.status === "experimental") {
|
|
181
|
+
warnings.push({
|
|
182
|
+
type: "experimental",
|
|
183
|
+
component: name,
|
|
184
|
+
message: `"${name}" is experimental and may change without notice`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 5. Category gap analysis
|
|
190
|
+
const selectedCategories = new Set(
|
|
191
|
+
components.map((name) => segments[name].meta.category)
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
for (const [category, affinities] of Object.entries(CATEGORY_AFFINITIES)) {
|
|
195
|
+
if (!selectedCategories.has(category)) continue;
|
|
196
|
+
|
|
197
|
+
for (const neededCategory of affinities) {
|
|
198
|
+
if (selectedCategories.has(neededCategory)) continue;
|
|
199
|
+
|
|
200
|
+
// Find the best component from the needed category
|
|
201
|
+
const candidate = findBestCategoryCandidate(
|
|
202
|
+
segments,
|
|
203
|
+
neededCategory,
|
|
204
|
+
selectedSet,
|
|
205
|
+
suggestedSet
|
|
206
|
+
);
|
|
207
|
+
if (candidate) {
|
|
208
|
+
suggestions.push({
|
|
209
|
+
component: candidate,
|
|
210
|
+
reason: `Compositions using "${category}" components often benefit from a "${neededCategory}" component`,
|
|
211
|
+
relationship: "category_gap",
|
|
212
|
+
sourceComponent: components.find(
|
|
213
|
+
(n) => segments[n].meta.category === category
|
|
214
|
+
)!,
|
|
215
|
+
});
|
|
216
|
+
suggestedSet.add(candidate);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { components, unknown, warnings, suggestions, guidelines };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Find the best candidate component from a given category.
|
|
226
|
+
* Prefers stable components and avoids already-selected or already-suggested ones.
|
|
227
|
+
*/
|
|
228
|
+
function findBestCategoryCandidate(
|
|
229
|
+
segments: Record<string, CompiledSegment>,
|
|
230
|
+
category: string,
|
|
231
|
+
selectedSet: Set<string>,
|
|
232
|
+
suggestedSet: Set<string>
|
|
233
|
+
): string | null {
|
|
234
|
+
let best: string | null = null;
|
|
235
|
+
let bestScore = -1;
|
|
236
|
+
|
|
237
|
+
for (const [name, segment] of Object.entries(segments)) {
|
|
238
|
+
if (segment.meta.category !== category) continue;
|
|
239
|
+
if (selectedSet.has(name) || suggestedSet.has(name)) continue;
|
|
240
|
+
|
|
241
|
+
const status = segment.meta.status ?? "stable";
|
|
242
|
+
let score = 0;
|
|
243
|
+
if (status === "stable") score = 3;
|
|
244
|
+
else if (status === "beta") score = 2;
|
|
245
|
+
else if (status === "experimental") score = 1;
|
|
246
|
+
// deprecated gets 0
|
|
247
|
+
|
|
248
|
+
if (score > bestScore) {
|
|
249
|
+
bestScore = score;
|
|
250
|
+
best = name;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return best;
|
|
255
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve, dirname } from 'node:path';
|
|
3
|
+
import { createJiti } from 'jiti';
|
|
4
|
+
import { BRAND } from './constants.js';
|
|
5
|
+
import type { SegmentsConfig } from './types.js';
|
|
6
|
+
import { segmentsConfigSchema } from './schema.js';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_CONFIG: SegmentsConfig = {
|
|
9
|
+
include: [
|
|
10
|
+
`src/**/*${BRAND.fileExtension}`, // *.segment.tsx files
|
|
11
|
+
'src/**/*.stories.tsx', // Storybook stories (auto-converted)
|
|
12
|
+
],
|
|
13
|
+
exclude: ['**/node_modules/**'],
|
|
14
|
+
components: ['src/**/index.tsx', 'src/**/*.tsx'],
|
|
15
|
+
framework: 'react',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Find the config file in the current directory or parent directories.
|
|
20
|
+
* Checks for both the current config file name and the legacy name.
|
|
21
|
+
*/
|
|
22
|
+
export function findConfigFile(startDir: string = process.cwd()): string | null {
|
|
23
|
+
let currentDir = startDir;
|
|
24
|
+
|
|
25
|
+
while (currentDir !== dirname(currentDir)) {
|
|
26
|
+
// Check for current config file name first
|
|
27
|
+
const configPath = resolve(currentDir, BRAND.configFile);
|
|
28
|
+
if (existsSync(configPath)) {
|
|
29
|
+
return configPath;
|
|
30
|
+
}
|
|
31
|
+
// Also check for legacy config file name
|
|
32
|
+
const legacyConfigPath = resolve(currentDir, BRAND.legacyConfigFile);
|
|
33
|
+
if (existsSync(legacyConfigPath)) {
|
|
34
|
+
return legacyConfigPath;
|
|
35
|
+
}
|
|
36
|
+
currentDir = dirname(currentDir);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Load and validate the config file
|
|
44
|
+
*/
|
|
45
|
+
export async function loadConfig(configPath?: string): Promise<{
|
|
46
|
+
config: SegmentsConfig;
|
|
47
|
+
configDir: string;
|
|
48
|
+
}> {
|
|
49
|
+
const resolvedPath = configPath ?? findConfigFile();
|
|
50
|
+
|
|
51
|
+
if (!resolvedPath) {
|
|
52
|
+
return {
|
|
53
|
+
config: DEFAULT_CONFIG,
|
|
54
|
+
configDir: process.cwd(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Use jiti to load TypeScript config files
|
|
60
|
+
const jiti = createJiti(import.meta.url, {
|
|
61
|
+
interopDefault: true,
|
|
62
|
+
});
|
|
63
|
+
const rawConfig = await jiti.import(resolvedPath);
|
|
64
|
+
|
|
65
|
+
const result = segmentsConfigSchema.safeParse(rawConfig);
|
|
66
|
+
|
|
67
|
+
if (!result.success) {
|
|
68
|
+
const errors = result.error.errors
|
|
69
|
+
.map((e) => ` - ${e.path.join('.')}: ${e.message}`)
|
|
70
|
+
.join('\n');
|
|
71
|
+
throw new Error(`Invalid config in ${resolvedPath}:\n${errors}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
config: { ...DEFAULT_CONFIG, ...result.data },
|
|
76
|
+
configDir: dirname(resolvedPath),
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof Error && error.message.includes('Invalid config')) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Failed to load config from ${resolvedPath}: ${error}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand constants for easy rebranding if domain availability requires it.
|
|
3
|
+
* All naming throughout the codebase should reference these constants.
|
|
4
|
+
*/
|
|
5
|
+
export const BRAND = {
|
|
6
|
+
/** Display name (e.g., "Fragments") */
|
|
7
|
+
name: "Fragments",
|
|
8
|
+
|
|
9
|
+
/** Lowercase name for file paths and CLI (e.g., "fragments") */
|
|
10
|
+
nameLower: "fragments",
|
|
11
|
+
|
|
12
|
+
/** File extension for fragment definition files (e.g., ".fragment.tsx") */
|
|
13
|
+
fileExtension: ".fragment.tsx",
|
|
14
|
+
|
|
15
|
+
/** Legacy file extension for segments (still supported for migration) */
|
|
16
|
+
legacyFileExtension: ".segment.tsx",
|
|
17
|
+
|
|
18
|
+
/** JSON file extension for compiled output */
|
|
19
|
+
jsonExtension: ".fragment.json",
|
|
20
|
+
|
|
21
|
+
/** Default output file name (e.g., "fragments.json") */
|
|
22
|
+
outFile: "fragments.json",
|
|
23
|
+
|
|
24
|
+
/** Config file name (e.g., "fragments.config.ts") */
|
|
25
|
+
configFile: "fragments.config.ts",
|
|
26
|
+
|
|
27
|
+
/** Legacy config file name (still supported for migration) */
|
|
28
|
+
legacyConfigFile: "segments.config.ts",
|
|
29
|
+
|
|
30
|
+
/** CLI command name (e.g., "fragments") */
|
|
31
|
+
cliCommand: "fragments",
|
|
32
|
+
|
|
33
|
+
/** Package scope (e.g., "@fragments") */
|
|
34
|
+
packageScope: "@fragments",
|
|
35
|
+
|
|
36
|
+
/** Directory for storing fragments, registry, and cache */
|
|
37
|
+
dataDir: ".fragments",
|
|
38
|
+
|
|
39
|
+
/** Components subdirectory within .fragments/ */
|
|
40
|
+
componentsDir: "components",
|
|
41
|
+
|
|
42
|
+
/** Registry file name */
|
|
43
|
+
registryFile: "registry.json",
|
|
44
|
+
|
|
45
|
+
/** Context file name (AI-ready markdown) */
|
|
46
|
+
contextFile: "context.md",
|
|
47
|
+
|
|
48
|
+
/** Screenshots subdirectory */
|
|
49
|
+
screenshotsDir: "screenshots",
|
|
50
|
+
|
|
51
|
+
/** Cache subdirectory (gitignored) */
|
|
52
|
+
cacheDir: "cache",
|
|
53
|
+
|
|
54
|
+
/** Diff output subdirectory (gitignored) */
|
|
55
|
+
diffDir: "diff",
|
|
56
|
+
|
|
57
|
+
/** Manifest filename */
|
|
58
|
+
manifestFile: "manifest.json",
|
|
59
|
+
|
|
60
|
+
/** Prefix for localStorage keys (e.g., "fragments-") */
|
|
61
|
+
storagePrefix: "fragments-",
|
|
62
|
+
|
|
63
|
+
/** Static viewer HTML file name */
|
|
64
|
+
viewerHtmlFile: "fragments-viewer.html",
|
|
65
|
+
|
|
66
|
+
/** MCP tool name prefix (e.g., "fragments_") */
|
|
67
|
+
mcpToolPrefix: "fragments_",
|
|
68
|
+
|
|
69
|
+
/** File extension for recipe definition files */
|
|
70
|
+
recipeFileExtension: ".recipe.ts",
|
|
71
|
+
|
|
72
|
+
/** Vite plugin namespace */
|
|
73
|
+
vitePluginNamespace: "fragments-core-shim",
|
|
74
|
+
} as const;
|
|
75
|
+
|
|
76
|
+
export type Brand = typeof BRAND;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Default configuration values for the service.
|
|
80
|
+
* These can be overridden in fragments.config.ts
|
|
81
|
+
*/
|
|
82
|
+
export const DEFAULTS = {
|
|
83
|
+
/** Default viewport dimensions */
|
|
84
|
+
viewport: {
|
|
85
|
+
width: 1280,
|
|
86
|
+
height: 800,
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/** Default diff threshold (percentage) */
|
|
90
|
+
diffThreshold: 5,
|
|
91
|
+
|
|
92
|
+
/** Browser pool size */
|
|
93
|
+
poolSize: 3,
|
|
94
|
+
|
|
95
|
+
/** Idle timeout before browser shutdown (ms) - 5 minutes */
|
|
96
|
+
idleTimeoutMs: 5 * 60 * 1000,
|
|
97
|
+
|
|
98
|
+
/** Delay after render before capture (ms) */
|
|
99
|
+
captureDelayMs: 100,
|
|
100
|
+
|
|
101
|
+
/** Font loading timeout (ms) */
|
|
102
|
+
fontTimeoutMs: 3000,
|
|
103
|
+
|
|
104
|
+
/** Default theme */
|
|
105
|
+
theme: "light" as const,
|
|
106
|
+
|
|
107
|
+
/** Dev server port */
|
|
108
|
+
port: 6006,
|
|
109
|
+
} as const;
|
|
110
|
+
|
|
111
|
+
export type Defaults = typeof DEFAULTS;
|