@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.
Files changed (166) hide show
  1. package/dist/bin.d.ts +1 -0
  2. package/dist/bin.js +502 -84
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-CJEGT3WD.js → chunk-566BNPQZ.js} +21 -6
  5. package/dist/chunk-566BNPQZ.js.map +1 -0
  6. package/dist/{chunk-WI6SLMSO.js → chunk-CAMXG5HJ.js} +5 -5
  7. package/dist/chunk-D2CDBRNU.js +2 -0
  8. package/dist/{chunk-YMPGYEWK.js → chunk-D5PYOXEI.js} +2 -2
  9. package/dist/{chunk-NGIMCIK2.js → chunk-OQO55NKV.js} +405 -34
  10. package/dist/chunk-OQO55NKV.js.map +1 -0
  11. package/dist/{chunk-TOIE7VXF.js → chunk-PW7QTQA6.js} +2 -2
  12. package/dist/{chunk-AWYCDRPG.js → chunk-WXSR2II7.js} +2 -2
  13. package/dist/chunk-WXSR2II7.js.map +1 -0
  14. package/dist/{chunk-2JIKCJX3.js → chunk-ZDA3PLQ6.js} +17 -14
  15. package/dist/chunk-ZDA3PLQ6.js.map +1 -0
  16. package/dist/core/index.d.ts +1 -2092
  17. package/dist/core/index.js +26 -21
  18. package/dist/{discovery-Z4RDDFVR.js → discovery-NEOY4MPN.js} +3 -3
  19. package/dist/generate-BGKTKO6E.js +459 -0
  20. package/dist/generate-BGKTKO6E.js.map +1 -0
  21. package/dist/index.d.ts +3 -5
  22. package/dist/index.js +7 -8
  23. package/dist/index.js.map +1 -1
  24. package/dist/{init-KSAAS7X3.js → init-Q53R5Q2T.js} +66 -76
  25. package/dist/init-Q53R5Q2T.js.map +1 -0
  26. package/dist/mcp-bin.js +5 -7
  27. package/dist/mcp-bin.js.map +1 -1
  28. package/dist/scan-OQU7M4GH.js +14 -0
  29. package/dist/scan-generate-T5QNUG7N.js +691 -0
  30. package/dist/scan-generate-T5QNUG7N.js.map +1 -0
  31. package/dist/{service-A5GIGGGK.js → service-TQYWY65E.js} +4 -5
  32. package/dist/{static-viewer-NSODM5VX.js → static-viewer-NUBFPKWH.js} +4 -5
  33. package/dist/static-viewer-NUBFPKWH.js.map +1 -0
  34. package/dist/{test-RPWZAYSJ.js → test-2CSOSS3B.js} +4 -5
  35. package/dist/{test-RPWZAYSJ.js.map → test-2CSOSS3B.js.map} +1 -1
  36. package/dist/{tokens-NIXSZRX7.js → tokens-DXEGYTOJ.js} +6 -7
  37. package/dist/{tokens-NIXSZRX7.js.map → tokens-DXEGYTOJ.js.map} +1 -1
  38. package/dist/{viewer-SBTJDMP7.js → viewer-DBEPYM3G.js} +245 -23
  39. package/dist/viewer-DBEPYM3G.js.map +1 -0
  40. package/package.json +2 -1
  41. package/src/bin.ts +33 -1
  42. package/src/build.ts +13 -3
  43. package/src/commands/__tests__/scan-generate.test.ts +308 -0
  44. package/src/commands/build.ts +16 -2
  45. package/src/commands/generate.ts +383 -68
  46. package/src/commands/init.ts +81 -56
  47. package/src/commands/perf.ts +1 -1
  48. package/src/commands/scan-generate.ts +1013 -0
  49. package/src/commands/setup.ts +499 -0
  50. package/src/core/auto-props.ts +1 -1
  51. package/src/core/bundle-measurer.ts +2 -2
  52. package/src/core/config.ts +16 -4
  53. package/src/core/discovery.ts +2 -2
  54. package/src/core/generators/context.ts +1 -1
  55. package/src/core/generators/registry.ts +3 -3
  56. package/src/core/generators/typescript-extractor.ts +11 -1
  57. package/src/core/graph-extractor.ts +1 -1
  58. package/src/core/index.ts +3 -190
  59. package/src/core/loader.ts +2 -2
  60. package/src/core/parser.ts +1 -1
  61. package/src/core/previewLoader.ts +1 -1
  62. package/src/index.ts +2 -2
  63. package/src/migrate/converter.ts +9 -1
  64. package/src/migrate/parser.ts +2 -0
  65. package/src/migrate/types.ts +2 -0
  66. package/src/service/snippet-validation.test.ts +1 -1
  67. package/src/service/snippet-validation.ts +2 -2
  68. package/src/setup.ts +69 -24
  69. package/src/viewer/__tests__/viewer-integration.test.ts +4 -10
  70. package/src/viewer/components/AccessibilityPanel.tsx +305 -312
  71. package/src/viewer/components/ActionsPanel.tsx +31 -29
  72. package/src/viewer/components/AllVariantsPreview.tsx +78 -0
  73. package/src/viewer/components/App.tsx +187 -740
  74. package/src/viewer/components/BottomPanel.tsx +228 -132
  75. package/src/viewer/components/CodePanel.tsx +1 -1
  76. package/src/viewer/components/CommandPalette.tsx +7 -10
  77. package/src/viewer/components/ComponentDocView.tsx +164 -0
  78. package/src/viewer/components/ComponentGraph.tsx +111 -142
  79. package/src/viewer/components/ContractPanel.tsx +6 -6
  80. package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
  81. package/src/viewer/components/FigmaEmbed.tsx +20 -18
  82. package/src/viewer/components/FragmentEditor.tsx +92 -115
  83. package/src/viewer/components/HeaderSearch.tsx +24 -0
  84. package/src/viewer/components/HealthDashboard.tsx +16 -2
  85. package/src/viewer/components/Icons.tsx +9 -0
  86. package/src/viewer/components/InteractionsPanel.tsx +101 -117
  87. package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
  88. package/src/viewer/components/LandingPage.tsx +3 -3
  89. package/src/viewer/components/LeftSidebar.tsx +141 -63
  90. package/src/viewer/components/LoadErrorMessage.tsx +102 -0
  91. package/src/viewer/components/MultiViewportPreview.tsx +61 -142
  92. package/src/viewer/components/NoVariantsMessage.tsx +59 -0
  93. package/src/viewer/components/PanelShell.tsx +161 -0
  94. package/src/viewer/components/PerformancePanel.tsx +31 -28
  95. package/src/viewer/components/PreviewArea.tsx +1 -1
  96. package/src/viewer/components/PreviewAside.tsx +168 -0
  97. package/src/viewer/components/PreviewFrameHost.tsx +3 -3
  98. package/src/viewer/components/PropsEditor.tsx +70 -156
  99. package/src/viewer/components/ResizablePanel.tsx +103 -263
  100. package/src/viewer/components/RightSidebar.tsx +3 -9
  101. package/src/viewer/components/SkeletonLoader.tsx +13 -13
  102. package/src/viewer/components/TokenStylePanel.tsx +182 -209
  103. package/src/viewer/components/TopToolbar.tsx +159 -0
  104. package/src/viewer/components/VariantMatrix.tsx +42 -86
  105. package/src/viewer/components/VariantTabs.tsx +3 -3
  106. package/src/viewer/components/ViewerHeader.tsx +69 -0
  107. package/src/viewer/components/WebMCPDevTools.tsx +17 -23
  108. package/src/viewer/components/viewer-utils.ts +16 -0
  109. package/src/viewer/entry.tsx +5 -0
  110. package/src/viewer/hooks/useAppState.ts +27 -4
  111. package/src/viewer/hooks/usePreviewBridge.ts +2 -2
  112. package/src/viewer/preview-frame.html +6 -12
  113. package/src/viewer/server.ts +169 -2
  114. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
  115. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
  116. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
  117. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +6 -18
  118. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
  119. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +5 -16
  120. package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
  121. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
  122. package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
  123. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +114 -0
  124. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
  125. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
  126. package/src/viewer/vendor/shared/src/index.ts +8 -0
  127. package/src/viewer/vendor/shared/src/types.ts +12 -0
  128. package/src/viewer/vite-plugin.ts +109 -4
  129. package/dist/chunk-2JIKCJX3.js.map +0 -1
  130. package/dist/chunk-AWYCDRPG.js.map +0 -1
  131. package/dist/chunk-CJEGT3WD.js.map +0 -1
  132. package/dist/chunk-EKLMXTWU.js +0 -80
  133. package/dist/chunk-EKLMXTWU.js.map +0 -1
  134. package/dist/chunk-GOVI6COW.js +0 -195
  135. package/dist/chunk-GOVI6COW.js.map +0 -1
  136. package/dist/chunk-NGIMCIK2.js.map +0 -1
  137. package/dist/defineFragment-D0UTve-I.d.ts +0 -665
  138. package/dist/generate-35OIMW4Y.js +0 -252
  139. package/dist/generate-35OIMW4Y.js.map +0 -1
  140. package/dist/init-KSAAS7X3.js.map +0 -1
  141. package/dist/scan-65RH3QMM.js +0 -15
  142. package/dist/viewer-SBTJDMP7.js.map +0 -1
  143. package/src/core/__tests__/preview-runtime.test.tsx +0 -111
  144. package/src/core/composition.test.ts +0 -262
  145. package/src/core/composition.ts +0 -318
  146. package/src/core/constants.ts +0 -114
  147. package/src/core/context.ts +0 -2
  148. package/src/core/defineFragment.ts +0 -141
  149. package/src/core/figma.ts +0 -263
  150. package/src/core/fragment-types.ts +0 -214
  151. package/src/core/performance-presets.ts +0 -142
  152. package/src/core/preview-runtime.tsx +0 -144
  153. package/src/core/schema.ts +0 -221
  154. package/src/core/storyAdapter.test.ts +0 -571
  155. package/src/core/storyAdapter.ts +0 -761
  156. package/src/core/storybook-csf.ts +0 -11
  157. package/src/core/token-parser.ts +0 -321
  158. package/src/core/token-types.ts +0 -287
  159. package/src/core/types.ts +0 -762
  160. /package/dist/{chunk-WI6SLMSO.js.map → chunk-CAMXG5HJ.js.map} +0 -0
  161. /package/dist/{discovery-Z4RDDFVR.js.map → chunk-D2CDBRNU.js.map} +0 -0
  162. /package/dist/{chunk-YMPGYEWK.js.map → chunk-D5PYOXEI.js.map} +0 -0
  163. /package/dist/{chunk-TOIE7VXF.js.map → chunk-PW7QTQA6.js.map} +0 -0
  164. /package/dist/{scan-65RH3QMM.js.map → discovery-NEOY4MPN.js.map} +0 -0
  165. /package/dist/{service-A5GIGGGK.js.map → scan-OQU7M4GH.js.map} +0 -0
  166. /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
+ });
@@ -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
- console.log(pc.green(`✓ Built ${result.fragmentCount} fragment(s)`));
116
- console.log(pc.dim(` Output: ${result.outputPath}\n`));
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