@fragments-sdk/cli 0.9.0 → 0.10.0
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.d.ts +1 -0
- package/dist/bin.js +502 -84
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-CJEGT3WD.js → chunk-566BNPQZ.js} +21 -6
- package/dist/chunk-566BNPQZ.js.map +1 -0
- package/dist/{chunk-WI6SLMSO.js → chunk-CAMXG5HJ.js} +5 -5
- package/dist/chunk-D2CDBRNU.js +2 -0
- package/dist/{chunk-YMPGYEWK.js → chunk-D5PYOXEI.js} +2 -2
- package/dist/{chunk-NGIMCIK2.js → chunk-OQO55NKV.js} +405 -34
- package/dist/chunk-OQO55NKV.js.map +1 -0
- package/dist/{chunk-TOIE7VXF.js → chunk-PW7QTQA6.js} +2 -2
- package/dist/{chunk-AWYCDRPG.js → chunk-WXSR2II7.js} +2 -2
- package/dist/chunk-WXSR2II7.js.map +1 -0
- package/dist/{chunk-2JIKCJX3.js → chunk-ZDA3PLQ6.js} +17 -14
- package/dist/chunk-ZDA3PLQ6.js.map +1 -0
- package/dist/core/index.d.ts +1 -2092
- package/dist/core/index.js +26 -21
- package/dist/{discovery-Z4RDDFVR.js → discovery-NEOY4MPN.js} +3 -3
- package/dist/generate-BGKTKO6E.js +459 -0
- package/dist/generate-BGKTKO6E.js.map +1 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -1
- package/dist/{init-KSAAS7X3.js → init-Q53R5Q2T.js} +66 -76
- package/dist/init-Q53R5Q2T.js.map +1 -0
- package/dist/mcp-bin.js +5 -7
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-OQU7M4GH.js +14 -0
- package/dist/scan-generate-T5QNUG7N.js +691 -0
- package/dist/scan-generate-T5QNUG7N.js.map +1 -0
- package/dist/{service-A5GIGGGK.js → service-TQYWY65E.js} +4 -5
- package/dist/{static-viewer-NSODM5VX.js → static-viewer-NUBFPKWH.js} +4 -5
- package/dist/static-viewer-NUBFPKWH.js.map +1 -0
- package/dist/{test-RPWZAYSJ.js → test-2CSOSS3B.js} +4 -5
- package/dist/{test-RPWZAYSJ.js.map → test-2CSOSS3B.js.map} +1 -1
- package/dist/{tokens-NIXSZRX7.js → tokens-DXEGYTOJ.js} +6 -7
- package/dist/{tokens-NIXSZRX7.js.map → tokens-DXEGYTOJ.js.map} +1 -1
- package/dist/{viewer-SBTJDMP7.js → viewer-DBEPYM3G.js} +245 -23
- package/dist/viewer-DBEPYM3G.js.map +1 -0
- package/package.json +2 -1
- package/src/bin.ts +33 -1
- package/src/build.ts +13 -3
- package/src/commands/__tests__/scan-generate.test.ts +308 -0
- package/src/commands/build.ts +16 -2
- package/src/commands/generate.ts +383 -68
- package/src/commands/init.ts +81 -56
- package/src/commands/perf.ts +1 -1
- package/src/commands/scan-generate.ts +1013 -0
- package/src/commands/setup.ts +499 -0
- package/src/core/auto-props.ts +1 -1
- package/src/core/bundle-measurer.ts +2 -2
- package/src/core/config.ts +16 -4
- package/src/core/discovery.ts +2 -2
- package/src/core/generators/context.ts +1 -1
- package/src/core/generators/registry.ts +3 -3
- package/src/core/generators/typescript-extractor.ts +11 -1
- package/src/core/graph-extractor.ts +1 -1
- package/src/core/index.ts +3 -190
- package/src/core/loader.ts +2 -2
- package/src/core/parser.ts +1 -1
- package/src/core/previewLoader.ts +1 -1
- package/src/index.ts +2 -2
- package/src/migrate/converter.ts +9 -1
- package/src/migrate/parser.ts +2 -0
- package/src/migrate/types.ts +2 -0
- package/src/service/snippet-validation.test.ts +1 -1
- package/src/service/snippet-validation.ts +2 -2
- package/src/setup.ts +69 -24
- package/src/viewer/__tests__/viewer-integration.test.ts +4 -10
- package/src/viewer/components/AccessibilityPanel.tsx +305 -312
- package/src/viewer/components/ActionsPanel.tsx +31 -29
- package/src/viewer/components/AllVariantsPreview.tsx +78 -0
- package/src/viewer/components/App.tsx +187 -740
- package/src/viewer/components/BottomPanel.tsx +228 -132
- package/src/viewer/components/CodePanel.tsx +1 -1
- package/src/viewer/components/CommandPalette.tsx +7 -10
- package/src/viewer/components/ComponentDocView.tsx +164 -0
- package/src/viewer/components/ComponentGraph.tsx +111 -142
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
- package/src/viewer/components/FigmaEmbed.tsx +20 -18
- package/src/viewer/components/FragmentEditor.tsx +92 -115
- package/src/viewer/components/HeaderSearch.tsx +24 -0
- package/src/viewer/components/HealthDashboard.tsx +16 -2
- package/src/viewer/components/Icons.tsx +9 -0
- package/src/viewer/components/InteractionsPanel.tsx +101 -117
- package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
- package/src/viewer/components/LandingPage.tsx +3 -3
- package/src/viewer/components/LeftSidebar.tsx +141 -63
- package/src/viewer/components/LoadErrorMessage.tsx +102 -0
- package/src/viewer/components/MultiViewportPreview.tsx +61 -142
- package/src/viewer/components/NoVariantsMessage.tsx +59 -0
- package/src/viewer/components/PanelShell.tsx +161 -0
- package/src/viewer/components/PerformancePanel.tsx +31 -28
- package/src/viewer/components/PreviewArea.tsx +1 -1
- package/src/viewer/components/PreviewAside.tsx +168 -0
- package/src/viewer/components/PreviewFrameHost.tsx +3 -3
- package/src/viewer/components/PropsEditor.tsx +70 -156
- package/src/viewer/components/ResizablePanel.tsx +103 -263
- package/src/viewer/components/RightSidebar.tsx +3 -9
- package/src/viewer/components/SkeletonLoader.tsx +13 -13
- package/src/viewer/components/TokenStylePanel.tsx +182 -209
- package/src/viewer/components/TopToolbar.tsx +159 -0
- package/src/viewer/components/VariantMatrix.tsx +42 -86
- package/src/viewer/components/VariantTabs.tsx +3 -3
- package/src/viewer/components/ViewerHeader.tsx +69 -0
- package/src/viewer/components/WebMCPDevTools.tsx +17 -23
- package/src/viewer/components/viewer-utils.ts +16 -0
- package/src/viewer/entry.tsx +5 -0
- package/src/viewer/hooks/useAppState.ts +27 -4
- package/src/viewer/hooks/usePreviewBridge.ts +2 -2
- package/src/viewer/preview-frame.html +6 -12
- package/src/viewer/server.ts +169 -2
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
- package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +6 -18
- package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
- package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +5 -16
- package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
- package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +114 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
- package/src/viewer/vendor/shared/src/index.ts +8 -0
- package/src/viewer/vendor/shared/src/types.ts +12 -0
- package/src/viewer/vite-plugin.ts +109 -4
- package/dist/chunk-2JIKCJX3.js.map +0 -1
- package/dist/chunk-AWYCDRPG.js.map +0 -1
- package/dist/chunk-CJEGT3WD.js.map +0 -1
- package/dist/chunk-EKLMXTWU.js +0 -80
- package/dist/chunk-EKLMXTWU.js.map +0 -1
- package/dist/chunk-GOVI6COW.js +0 -195
- package/dist/chunk-GOVI6COW.js.map +0 -1
- package/dist/chunk-NGIMCIK2.js.map +0 -1
- package/dist/defineFragment-D0UTve-I.d.ts +0 -665
- package/dist/generate-35OIMW4Y.js +0 -252
- package/dist/generate-35OIMW4Y.js.map +0 -1
- package/dist/init-KSAAS7X3.js.map +0 -1
- package/dist/scan-65RH3QMM.js +0 -15
- package/dist/viewer-SBTJDMP7.js.map +0 -1
- package/src/core/__tests__/preview-runtime.test.tsx +0 -111
- package/src/core/composition.test.ts +0 -262
- package/src/core/composition.ts +0 -318
- package/src/core/constants.ts +0 -114
- package/src/core/context.ts +0 -2
- package/src/core/defineFragment.ts +0 -141
- package/src/core/figma.ts +0 -263
- package/src/core/fragment-types.ts +0 -214
- package/src/core/performance-presets.ts +0 -142
- package/src/core/preview-runtime.tsx +0 -144
- package/src/core/schema.ts +0 -221
- package/src/core/storyAdapter.test.ts +0 -571
- package/src/core/storyAdapter.ts +0 -761
- package/src/core/storybook-csf.ts +0 -11
- package/src/core/token-parser.ts +0 -321
- package/src/core/token-types.ts +0 -287
- package/src/core/types.ts +0 -762
- /package/dist/{chunk-WI6SLMSO.js.map → chunk-CAMXG5HJ.js.map} +0 -0
- /package/dist/{discovery-Z4RDDFVR.js.map → chunk-D2CDBRNU.js.map} +0 -0
- /package/dist/{chunk-YMPGYEWK.js.map → chunk-D5PYOXEI.js.map} +0 -0
- /package/dist/{chunk-TOIE7VXF.js.map → chunk-PW7QTQA6.js.map} +0 -0
- /package/dist/{scan-65RH3QMM.js.map → discovery-NEOY4MPN.js.map} +0 -0
- /package/dist/{service-A5GIGGGK.js.map → scan-OQU7M4GH.js.map} +0 -0
- /package/dist/{static-viewer-NSODM5VX.js.map → service-TQYWY65E.js.map} +0 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { mkdtemp, writeFile, mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import {
|
|
6
|
+
extractComponentJSDocFromSource,
|
|
7
|
+
detectCompoundComponentsFromSource,
|
|
8
|
+
calculateFieldConfidence,
|
|
9
|
+
scanGenerate,
|
|
10
|
+
} from "../scan-generate.js";
|
|
11
|
+
import type { ExtractedProp } from "../../service/enhance/props-extractor.js";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// JSDoc Extraction
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
describe("extractComponentJSDocFromSource", () => {
|
|
18
|
+
it("extracts JSDoc from an exported function declaration", () => {
|
|
19
|
+
const source = `
|
|
20
|
+
/** A beautiful button component for user interactions. */
|
|
21
|
+
export function Button(props: ButtonProps) {
|
|
22
|
+
return <button>{props.children}</button>;
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
const result = extractComponentJSDocFromSource(source, "Button.tsx", "Button");
|
|
26
|
+
expect(result).toBe(
|
|
27
|
+
"A beautiful button component for user interactions."
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("extracts JSDoc from an exported const arrow function", () => {
|
|
32
|
+
const source = `
|
|
33
|
+
/** Card container for grouping related content. */
|
|
34
|
+
export const Card = (props: CardProps) => {
|
|
35
|
+
return <div>{props.children}</div>;
|
|
36
|
+
};
|
|
37
|
+
`;
|
|
38
|
+
const result = extractComponentJSDocFromSource(source, "Card.tsx", "Card");
|
|
39
|
+
expect(result).toBe("Card container for grouping related content.");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns null when there is no JSDoc", () => {
|
|
43
|
+
const source = `
|
|
44
|
+
export function Button(props: ButtonProps) {
|
|
45
|
+
return <button>{props.children}</button>;
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
const result = extractComponentJSDocFromSource(source, "Button.tsx", "Button");
|
|
49
|
+
expect(result).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("stops at JSDoc tags and only returns the description", () => {
|
|
53
|
+
const source = `
|
|
54
|
+
/**
|
|
55
|
+
* A toggle switch component.
|
|
56
|
+
* @param props - The switch props
|
|
57
|
+
* @example <Switch checked />
|
|
58
|
+
*/
|
|
59
|
+
export function Switch(props: SwitchProps) {
|
|
60
|
+
return <input type="checkbox" />;
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
const result = extractComponentJSDocFromSource(source, "Switch.tsx", "Switch");
|
|
64
|
+
expect(result).toBe("A toggle switch component.");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("extracts JSDoc from a default export function", () => {
|
|
68
|
+
const source = `
|
|
69
|
+
/** The main App component. */
|
|
70
|
+
export default function App() {
|
|
71
|
+
return <div />;
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
const result = extractComponentJSDocFromSource(source, "App.tsx");
|
|
75
|
+
expect(result).toBe("The main App component.");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Compound Component Detection
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
describe("detectCompoundComponentsFromSource", () => {
|
|
84
|
+
it("detects Object.assign with shorthand properties", () => {
|
|
85
|
+
const source = `
|
|
86
|
+
const Root = () => <div />;
|
|
87
|
+
const Header = () => <header />;
|
|
88
|
+
const Body = () => <main />;
|
|
89
|
+
const Footer = () => <footer />;
|
|
90
|
+
|
|
91
|
+
export const Card = Object.assign(Root, { Header, Body, Footer });
|
|
92
|
+
`;
|
|
93
|
+
const result = detectCompoundComponentsFromSource(source, "Card.tsx");
|
|
94
|
+
expect(result).toEqual(["Header", "Body", "Footer"]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("detects Object.assign with renamed properties", () => {
|
|
98
|
+
const source = `
|
|
99
|
+
const CardRoot = () => <div />;
|
|
100
|
+
const CardHeader = () => <header />;
|
|
101
|
+
|
|
102
|
+
export const Card = Object.assign(CardRoot, { Header: CardHeader });
|
|
103
|
+
`;
|
|
104
|
+
const result = detectCompoundComponentsFromSource(source, "Card.tsx");
|
|
105
|
+
expect(result).toEqual(["Header"]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("returns empty array when no compound pattern found", () => {
|
|
109
|
+
const source = `
|
|
110
|
+
export function Button() {
|
|
111
|
+
return <button />;
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
const result = detectCompoundComponentsFromSource(source, "Button.tsx");
|
|
115
|
+
expect(result).toEqual([]);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Confidence Scoring
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
describe("calculateFieldConfidence", () => {
|
|
124
|
+
const makeProps = (names: string[]): ExtractedProp[] =>
|
|
125
|
+
names.map((name) => ({
|
|
126
|
+
name,
|
|
127
|
+
type: "string",
|
|
128
|
+
propType: { type: "string" as const },
|
|
129
|
+
description: "",
|
|
130
|
+
required: false,
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
it("gives +30 for extracted props", () => {
|
|
134
|
+
const result = calculateFieldConfidence({
|
|
135
|
+
component: { name: "Foo", sourcePath: "/foo.tsx", relativePath: "foo.tsx" },
|
|
136
|
+
props: {
|
|
137
|
+
filePath: "/foo.tsx",
|
|
138
|
+
componentName: "Foo",
|
|
139
|
+
props: makeProps(["children"]),
|
|
140
|
+
success: true,
|
|
141
|
+
warnings: [],
|
|
142
|
+
},
|
|
143
|
+
jsDoc: null,
|
|
144
|
+
compoundChildren: [],
|
|
145
|
+
storyVariants: [],
|
|
146
|
+
});
|
|
147
|
+
// +30 (props) + 0 (no JSDoc → TODO) + 0 (category fallback → TODO) + 0 (no stories) + 10 (all resolved) + 0 (no defaults) = 40
|
|
148
|
+
// But "children" prop → category "Layout" (+10)
|
|
149
|
+
// So: 30 + 10 + 10 = 50
|
|
150
|
+
expect(result.score).toBe(50);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("gives +15 for JSDoc description", () => {
|
|
154
|
+
const result = calculateFieldConfidence({
|
|
155
|
+
component: { name: "Foo", sourcePath: "/foo.tsx", relativePath: "foo.tsx" },
|
|
156
|
+
props: null,
|
|
157
|
+
jsDoc: "A component",
|
|
158
|
+
compoundChildren: [],
|
|
159
|
+
storyVariants: [],
|
|
160
|
+
});
|
|
161
|
+
// +15 (JSDoc), no props, category fallback → TODO, no stories
|
|
162
|
+
expect(result.score).toBe(15);
|
|
163
|
+
expect(result.todoFields).not.toContain("meta.description");
|
|
164
|
+
expect(result.todoFields).toContain("meta.category");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("gives +25 for story variants", () => {
|
|
168
|
+
const result = calculateFieldConfidence({
|
|
169
|
+
component: { name: "Foo", sourcePath: "/foo.tsx", relativePath: "foo.tsx" },
|
|
170
|
+
props: null,
|
|
171
|
+
jsDoc: null,
|
|
172
|
+
compoundChildren: [],
|
|
173
|
+
storyVariants: [{ name: "Primary", args: {} }],
|
|
174
|
+
});
|
|
175
|
+
// +25 (stories)
|
|
176
|
+
expect(result.score).toBe(25);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("gives +5 for compound children", () => {
|
|
180
|
+
const result = calculateFieldConfidence({
|
|
181
|
+
component: { name: "Foo", sourcePath: "/foo.tsx", relativePath: "foo.tsx" },
|
|
182
|
+
props: null,
|
|
183
|
+
jsDoc: null,
|
|
184
|
+
compoundChildren: ["Header", "Body"],
|
|
185
|
+
storyVariants: [],
|
|
186
|
+
});
|
|
187
|
+
expect(result.score).toBe(5);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("always includes usage.when and usage.whenNot in TODOs", () => {
|
|
191
|
+
const result = calculateFieldConfidence({
|
|
192
|
+
component: { name: "Button", sourcePath: "/button.tsx", relativePath: "button.tsx" },
|
|
193
|
+
props: {
|
|
194
|
+
filePath: "/button.tsx",
|
|
195
|
+
componentName: "Button",
|
|
196
|
+
props: makeProps(["onClick"]),
|
|
197
|
+
success: true,
|
|
198
|
+
warnings: [],
|
|
199
|
+
},
|
|
200
|
+
jsDoc: "A button",
|
|
201
|
+
compoundChildren: [],
|
|
202
|
+
storyVariants: [{ name: "Primary", args: {} }],
|
|
203
|
+
});
|
|
204
|
+
expect(result.todoFields).toContain("usage.when");
|
|
205
|
+
expect(result.todoFields).toContain("usage.whenNot");
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Integration: scanGenerate against a temp component
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
describe("scanGenerate integration", () => {
|
|
214
|
+
let tmpDir: string;
|
|
215
|
+
|
|
216
|
+
beforeAll(async () => {
|
|
217
|
+
tmpDir = await mkdtemp(join(tmpdir(), "scan-gen-test-"));
|
|
218
|
+
|
|
219
|
+
// Create a simple component structure
|
|
220
|
+
const compDir = join(tmpDir, "components", "Button");
|
|
221
|
+
await mkdir(compDir, { recursive: true });
|
|
222
|
+
|
|
223
|
+
await writeFile(
|
|
224
|
+
join(compDir, "Button.tsx"),
|
|
225
|
+
`import React from 'react';
|
|
226
|
+
|
|
227
|
+
/** A primary button for user interactions. */
|
|
228
|
+
export interface ButtonProps {
|
|
229
|
+
/** Button label */
|
|
230
|
+
children: React.ReactNode;
|
|
231
|
+
/** Visual variant */
|
|
232
|
+
variant?: 'primary' | 'secondary';
|
|
233
|
+
/** Disabled state */
|
|
234
|
+
disabled?: boolean;
|
|
235
|
+
/** Click handler */
|
|
236
|
+
onClick?: () => void;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function Button({ children, variant = 'primary', disabled, onClick }: ButtonProps) {
|
|
240
|
+
return <button disabled={disabled} onClick={onClick}>{children}</button>;
|
|
241
|
+
}
|
|
242
|
+
`,
|
|
243
|
+
"utf-8"
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
afterAll(async () => {
|
|
248
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("generates a fragment file with correct structure", async () => {
|
|
252
|
+
const result = await scanGenerate({
|
|
253
|
+
scanPath: join(tmpDir, "components"),
|
|
254
|
+
force: true,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(result.success).toBe(true);
|
|
258
|
+
expect(result.generated.length).toBe(1);
|
|
259
|
+
expect(result.generated[0].name).toBe("Button");
|
|
260
|
+
expect(result.generated[0].confidence).toBeGreaterThan(0);
|
|
261
|
+
|
|
262
|
+
// Read the generated file and check structure
|
|
263
|
+
const { readFile: rf } = await import("node:fs/promises");
|
|
264
|
+
const content = await rf(
|
|
265
|
+
join(tmpDir, "components", "Button", "Button.fragment.tsx"),
|
|
266
|
+
"utf-8"
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// Check import uses @fragments-sdk/core
|
|
270
|
+
expect(content).toContain(
|
|
271
|
+
"import { defineFragment } from '@fragments-sdk/core'"
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Check confidence header
|
|
275
|
+
expect(content).toMatch(
|
|
276
|
+
/\/\/ Auto-generated by fragments init --scan \| Confidence: \d+\/100/
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Check TODO markers exist
|
|
280
|
+
expect(content).toContain("// TODO:");
|
|
281
|
+
|
|
282
|
+
// Check component name in meta
|
|
283
|
+
expect(content).toContain("name: 'Button'");
|
|
284
|
+
|
|
285
|
+
// Check JSDoc was picked up in description
|
|
286
|
+
expect(content).toContain("A primary button for user interactions.");
|
|
287
|
+
|
|
288
|
+
// Check category was inferred (Button → Actions)
|
|
289
|
+
expect(content).toContain("category: 'Actions'");
|
|
290
|
+
|
|
291
|
+
// Check props block has content
|
|
292
|
+
expect(content).toContain("children:");
|
|
293
|
+
expect(content).toContain("variant:");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("skips existing fragments without --force", async () => {
|
|
297
|
+
// First run already created the fragment
|
|
298
|
+
const result = await scanGenerate({
|
|
299
|
+
scanPath: join(tmpDir, "components"),
|
|
300
|
+
force: false,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
expect(result.skipped.length).toBe(1);
|
|
304
|
+
expect(result.skipped[0].name).toBe("Button");
|
|
305
|
+
expect(result.skipped[0].reason).toBe("Fragment already exists");
|
|
306
|
+
expect(result.generated.length).toBe(0);
|
|
307
|
+
});
|
|
308
|
+
});
|
package/src/commands/build.ts
CHANGED
|
@@ -112,8 +112,22 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
|
|
|
112
112
|
fragmentCount = result.fragmentCount;
|
|
113
113
|
outputPath = result.outputPath;
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
if (result.fragmentCount === 0 && result.errors.length === 0) {
|
|
116
|
+
// No compilable fragment files found — auto-fallback to scan
|
|
117
|
+
console.log(pc.dim('No compilable fragment files found. Falling back to source scan...\n'));
|
|
118
|
+
|
|
119
|
+
const scanResult = await scan({
|
|
120
|
+
config: options.config,
|
|
121
|
+
output: options.output,
|
|
122
|
+
verbose: options.verbose,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
fragmentCount = scanResult.componentCount;
|
|
126
|
+
outputPath = scanResult.outputPath;
|
|
127
|
+
} else {
|
|
128
|
+
console.log(pc.green(`✓ Built ${result.fragmentCount} fragment(s)`));
|
|
129
|
+
console.log(pc.dim(` Output: ${result.outputPath}\n`));
|
|
130
|
+
}
|
|
117
131
|
}
|
|
118
132
|
|
|
119
133
|
// Build .fragments/ directory if --registry or --registry-only
|