@fragments-sdk/cli 0.10.1 → 0.12.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.
Files changed (223) hide show
  1. package/dist/ai-client-I6MDWNYA.js +21 -0
  2. package/dist/bin.js +292 -367
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
  5. package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
  6. package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
  7. package/dist/chunk-GVDSFQ4E.js.map +1 -0
  8. package/dist/chunk-JJ2VRTBU.js +626 -0
  9. package/dist/chunk-JJ2VRTBU.js.map +1 -0
  10. package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
  11. package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
  12. package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
  13. package/dist/chunk-OQKMEFOS.js.map +1 -0
  14. package/dist/chunk-SXTKFDCR.js +104 -0
  15. package/dist/chunk-SXTKFDCR.js.map +1 -0
  16. package/dist/chunk-T5OMVL7E.js +443 -0
  17. package/dist/chunk-T5OMVL7E.js.map +1 -0
  18. package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
  19. package/dist/chunk-TPWGL2XS.js.map +1 -0
  20. package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
  21. package/dist/chunk-WFS63PCW.js.map +1 -0
  22. package/dist/core/index.js +9 -1
  23. package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
  24. package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
  25. package/dist/index.js +7 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/init-ZSX3NRCZ.js +636 -0
  28. package/dist/init-ZSX3NRCZ.js.map +1 -0
  29. package/dist/mcp-bin.js +2 -2
  30. package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
  31. package/dist/scan-generate-SYU4PYZD.js +1115 -0
  32. package/dist/scan-generate-SYU4PYZD.js.map +1 -0
  33. package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
  34. package/dist/snapshot-XOISO2IS.js +139 -0
  35. package/dist/snapshot-XOISO2IS.js.map +1 -0
  36. package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
  37. package/dist/static-viewer-5GXH2MGE.js.map +1 -0
  38. package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
  39. package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
  40. package/dist/{viewer-DNMNC5VS.js → viewer-7ZEAFBVN.js} +80 -58
  41. package/dist/viewer-7ZEAFBVN.js.map +1 -0
  42. package/package.json +6 -14
  43. package/src/ai-client.ts +156 -0
  44. package/src/bin.ts +74 -2
  45. package/src/build.ts +95 -33
  46. package/src/commands/__tests__/drift-sync.test.ts +252 -0
  47. package/src/commands/__tests__/scan-generate.test.ts +497 -45
  48. package/src/commands/enhance.ts +11 -35
  49. package/src/commands/init.ts +296 -193
  50. package/src/commands/scan-generate.ts +740 -139
  51. package/src/commands/scan.ts +37 -32
  52. package/src/commands/setup.ts +143 -52
  53. package/src/commands/snapshot.ts +197 -0
  54. package/src/commands/sync.ts +357 -0
  55. package/src/commands/validate.ts +43 -1
  56. package/src/core/component-extractor.test.ts +282 -0
  57. package/src/core/component-extractor.ts +1030 -0
  58. package/src/core/discovery.ts +93 -7
  59. package/src/service/enhance/props-extractor.ts +235 -13
  60. package/src/validators.ts +236 -0
  61. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  62. package/src/viewer/server.ts +37 -22
  63. package/src/viewer/vite-plugin.ts +25 -9
  64. package/dist/chunk-5G3VZH43.js.map +0 -1
  65. package/dist/chunk-OQO55NKV.js.map +0 -1
  66. package/dist/chunk-WXSR2II7.js.map +0 -1
  67. package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
  68. package/dist/init-NDQXUWDU.js +0 -796
  69. package/dist/init-NDQXUWDU.js.map +0 -1
  70. package/dist/scan-generate-SJAN5MVI.js +0 -691
  71. package/dist/scan-generate-SJAN5MVI.js.map +0 -1
  72. package/dist/viewer-DNMNC5VS.js.map +0 -1
  73. package/src/ai.ts +0 -266
  74. package/src/commands/init-framework.ts +0 -414
  75. package/src/mcp/bin.ts +0 -36
  76. package/src/migrate/bin.ts +0 -114
  77. package/src/theme/index.ts +0 -77
  78. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  79. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  80. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  81. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  82. package/src/viewer/assets/fragments-logo.ts +0 -4
  83. package/src/viewer/assets/fragments_logo.png +0 -0
  84. package/src/viewer/bin.ts +0 -86
  85. package/src/viewer/cli/health.ts +0 -256
  86. package/src/viewer/cli/index.ts +0 -33
  87. package/src/viewer/cli/scan.ts +0 -124
  88. package/src/viewer/cli/utils.ts +0 -174
  89. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  90. package/src/viewer/components/ActionCapture.tsx +0 -172
  91. package/src/viewer/components/ActionsPanel.tsx +0 -332
  92. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  93. package/src/viewer/components/App.tsx +0 -582
  94. package/src/viewer/components/BottomPanel.tsx +0 -288
  95. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  96. package/src/viewer/components/CodePanel.tsx +0 -118
  97. package/src/viewer/components/CommandPalette.tsx +0 -392
  98. package/src/viewer/components/ComponentDocView.tsx +0 -164
  99. package/src/viewer/components/ComponentGraph.tsx +0 -380
  100. package/src/viewer/components/ComponentHeader.tsx +0 -88
  101. package/src/viewer/components/ContractPanel.tsx +0 -241
  102. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  103. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  104. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  105. package/src/viewer/components/FragmentEditor.tsx +0 -525
  106. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  107. package/src/viewer/components/HeaderSearch.tsx +0 -24
  108. package/src/viewer/components/HealthDashboard.tsx +0 -441
  109. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  110. package/src/viewer/components/Icons.tsx +0 -479
  111. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  112. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  113. package/src/viewer/components/IsolatedRender.tsx +0 -113
  114. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  115. package/src/viewer/components/LandingPage.tsx +0 -421
  116. package/src/viewer/components/Layout.tsx +0 -27
  117. package/src/viewer/components/LeftSidebar.tsx +0 -472
  118. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  119. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  120. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  121. package/src/viewer/components/PanelShell.tsx +0 -161
  122. package/src/viewer/components/PerformancePanel.tsx +0 -304
  123. package/src/viewer/components/PreviewArea.tsx +0 -472
  124. package/src/viewer/components/PreviewAside.tsx +0 -168
  125. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  126. package/src/viewer/components/PreviewPane.tsx +0 -149
  127. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  128. package/src/viewer/components/PropsEditor.tsx +0 -506
  129. package/src/viewer/components/PropsTable.tsx +0 -111
  130. package/src/viewer/components/RelationsSection.tsx +0 -88
  131. package/src/viewer/components/ResizablePanel.tsx +0 -271
  132. package/src/viewer/components/RightSidebar.tsx +0 -102
  133. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  134. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  135. package/src/viewer/components/Sidebar.tsx +0 -169
  136. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  137. package/src/viewer/components/ThemeProvider.tsx +0 -42
  138. package/src/viewer/components/Toast.tsx +0 -3
  139. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  140. package/src/viewer/components/TopToolbar.tsx +0 -159
  141. package/src/viewer/components/UsageSection.tsx +0 -95
  142. package/src/viewer/components/VariantMatrix.tsx +0 -388
  143. package/src/viewer/components/VariantRenderer.tsx +0 -131
  144. package/src/viewer/components/VariantTabs.tsx +0 -40
  145. package/src/viewer/components/ViewerHeader.tsx +0 -69
  146. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  147. package/src/viewer/components/ViewportSelector.tsx +0 -172
  148. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  149. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  150. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  151. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  152. package/src/viewer/components/viewer-utils.ts +0 -16
  153. package/src/viewer/composition-renderer.ts +0 -381
  154. package/src/viewer/constants/index.ts +0 -1
  155. package/src/viewer/constants/ui.ts +0 -166
  156. package/src/viewer/entry.tsx +0 -335
  157. package/src/viewer/hooks/index.ts +0 -2
  158. package/src/viewer/hooks/useA11yCache.ts +0 -383
  159. package/src/viewer/hooks/useA11yService.ts +0 -364
  160. package/src/viewer/hooks/useActions.ts +0 -138
  161. package/src/viewer/hooks/useAppState.ts +0 -147
  162. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  163. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  164. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  165. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  166. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  167. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  168. package/src/viewer/hooks/useUrlState.ts +0 -318
  169. package/src/viewer/hooks/useViewSettings.ts +0 -111
  170. package/src/viewer/index.html +0 -28
  171. package/src/viewer/intelligence/healthReport.ts +0 -505
  172. package/src/viewer/intelligence/styleDrift.ts +0 -340
  173. package/src/viewer/intelligence/usageScanner.ts +0 -309
  174. package/src/viewer/jsx-parser.ts +0 -486
  175. package/src/viewer/preview-frame-entry.tsx +0 -25
  176. package/src/viewer/preview-frame.html +0 -125
  177. package/src/viewer/public/favicon.ico +0 -0
  178. package/src/viewer/render-template.html +0 -68
  179. package/src/viewer/styles/globals.css +0 -278
  180. package/src/viewer/types/a11y.ts +0 -197
  181. package/src/viewer/utils/a11y-fixes.ts +0 -509
  182. package/src/viewer/utils/actionExport.ts +0 -372
  183. package/src/viewer/utils/colorSchemes.ts +0 -201
  184. package/src/viewer/utils/detectRelationships.ts +0 -256
  185. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  186. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  187. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  188. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  189. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  190. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  191. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  192. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  193. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  194. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  195. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  196. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  197. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  198. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -137
  199. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  200. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  201. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  202. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  203. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  204. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  205. package/src/viewer/vendor/shared/src/index.ts +0 -34
  206. package/src/viewer/vendor/shared/src/types.ts +0 -53
  207. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  208. package/src/viewer/webmcp/analytics.ts +0 -165
  209. package/src/viewer/webmcp/index.ts +0 -3
  210. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  211. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  212. package/src/viewer/webmcp/scan-utils.ts +0 -135
  213. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  214. package/src/viewer/webmcp/viewer-state.ts +0 -45
  215. /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
  216. /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
  217. /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
  218. /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
  219. /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
  220. /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
  221. /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
  222. /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
  223. /package/dist/{tokens-CE46OTMD.js.map → tokens-T6SIVUT5.js.map} +0 -0
@@ -7,63 +7,101 @@ import { tmpdir } from "node:os";
7
7
  import { discoverInstalledFragments } from "../../core/discovery.js";
8
8
 
9
9
  /**
10
- * Integration tests for the consolidated viewer.
10
+ * Integration tests for the viewer architecture.
11
11
  *
12
- * After packages were merged into @fragments-sdk/cli, the viewer's
13
- * path resolution changed. These tests verify that:
14
- * - Viewer assets (HTML, TSX entry points) are found at the correct paths
15
- * - The @fragments-sdk/cli/core alias resolves to the consolidated core source
12
+ * After the viewer extraction (Delivery A), viewer UI code lives in
13
+ * packages/viewer/ while CLI keeps the Vite server and plugin.
14
+ * These tests verify:
15
+ * - Viewer assets (HTML, TSX entry points) are found in packages/viewer/
16
+ * - The CLI server/plugin reference the viewer package correctly
17
+ * - The @fragments-sdk/cli/core alias resolves to consolidated core source
16
18
  * - The virtual module generates valid import statements
17
- * - The Vite config references correct file system locations
18
19
  */
19
20
 
20
- // Simulate the same path resolution used in server.ts and vite-plugin.ts
21
- // At runtime, __dirname is dist/. In tests (vitest), it's the source dir.
22
21
  const testDir = dirname(fileURLToPath(import.meta.url));
23
22
  const viewerDir = resolve(testDir, "..");
24
23
  const cliPackageRoot = resolve(viewerDir, "../..");
24
+ const packagesRoot = resolve(cliPackageRoot, "..");
25
+ const viewerPackageRoot = resolve(packagesRoot, "viewer");
26
+ const viewerSrc = resolve(viewerPackageRoot, "src");
25
27
 
26
- describe("viewer path resolution", () => {
27
- it("viewerRoot resolves to a directory containing index.html", () => {
28
- // server.ts: viewerRoot = resolve(cliPackageRoot, "src/viewer")
29
- const viewerRoot = resolve(cliPackageRoot, "src/viewer");
30
- expect(existsSync(resolve(viewerRoot, "index.html"))).toBe(true);
28
+ describe("viewer package path resolution", () => {
29
+ it("viewer package root exists", () => {
30
+ expect(existsSync(viewerPackageRoot)).toBe(true);
31
31
  });
32
32
 
33
- it("viewerRoot contains entry.tsx", () => {
34
- const viewerRoot = resolve(cliPackageRoot, "src/viewer");
35
- expect(existsSync(resolve(viewerRoot, "entry.tsx"))).toBe(true);
33
+ it("viewer package contains index.html at root", () => {
34
+ expect(existsSync(resolve(viewerPackageRoot, "index.html"))).toBe(true);
36
35
  });
37
36
 
38
- it("viewerRoot contains preview-frame.html", () => {
39
- const viewerRoot = resolve(cliPackageRoot, "src/viewer");
40
- expect(existsSync(resolve(viewerRoot, "preview-frame.html"))).toBe(true);
37
+ it("viewer package contains entry.tsx", () => {
38
+ expect(existsSync(resolve(viewerSrc, "entry.tsx"))).toBe(true);
41
39
  });
42
40
 
43
- it("viewerRoot contains preview-frame-entry.tsx", () => {
44
- const viewerRoot = resolve(cliPackageRoot, "src/viewer");
45
- expect(existsSync(resolve(viewerRoot, "preview-frame-entry.tsx"))).toBe(true);
41
+ it("viewer package contains preview-frame.html in src/", () => {
42
+ expect(existsSync(resolve(viewerSrc, "preview-frame.html"))).toBe(true);
46
43
  });
47
44
 
48
- it("viewerRoot contains render-template.html", () => {
49
- const viewerRoot = resolve(cliPackageRoot, "src/viewer");
50
- expect(existsSync(resolve(viewerRoot, "render-template.html"))).toBe(true);
45
+ it("viewer package contains preview-frame-entry.tsx", () => {
46
+ expect(existsSync(resolve(viewerSrc, "preview-frame-entry.tsx"))).toBe(
47
+ true
48
+ );
49
+ });
50
+
51
+ it("viewer package contains render-template.html in src/", () => {
52
+ expect(existsSync(resolve(viewerSrc, "render-template.html"))).toBe(true);
51
53
  });
52
54
 
53
- it("viewerRoot does not contain tailwind.config.js (removed in Tailwind→Fragments migration)", () => {
54
- const viewerRoot = resolve(cliPackageRoot, "src/viewer");
55
- expect(existsSync(resolve(viewerRoot, "tailwind.config.js"))).toBe(false);
55
+ it("viewer package contains shared/index.ts barrel", () => {
56
+ expect(existsSync(resolve(viewerSrc, "shared/index.ts"))).toBe(true);
56
57
  });
57
58
 
58
- it("viewerRoot contains styles/globals.css", () => {
59
- const viewerRoot = resolve(cliPackageRoot, "src/viewer");
60
- expect(existsSync(resolve(viewerRoot, "styles/globals.css"))).toBe(true);
59
+ it("viewer package contains app/index.ts barrel", () => {
60
+ expect(existsSync(resolve(viewerSrc, "app/index.ts"))).toBe(true);
61
+ });
62
+ });
63
+
64
+ describe("CLI viewer server references viewer package", () => {
65
+ it("server.ts has resolveViewerRoot function for monorepo and npm resolution", async () => {
66
+ const serverPath = resolve(viewerDir, "server.ts");
67
+ const content = await readFile(serverPath, "utf-8");
68
+
69
+ expect(content).toContain("function resolveViewerRoot(nodeModulesDir: string)");
70
+ // Checks monorepo path first
71
+ expect(content).toContain('resolve(packagesRoot, "viewer")');
72
+ // Falls back to npm-installed path
73
+ expect(content).toContain("@fragments-sdk/viewer");
74
+ });
75
+
76
+ it("server.ts sets up viewer subpath aliases", async () => {
77
+ const serverPath = resolve(viewerDir, "server.ts");
78
+ const content = await readFile(serverPath, "utf-8");
79
+
80
+ expect(content).toContain("@fragments-sdk/viewer/shared");
81
+ expect(content).toContain("@fragments-sdk/viewer/app");
82
+ });
83
+
84
+ it("server.ts does not reference old resolveFragmentsPackage for core", async () => {
85
+ const serverPath = resolve(viewerDir, "server.ts");
86
+ const content = await readFile(serverPath, "utf-8");
87
+
88
+ expect(content).not.toContain('resolveFragmentsPackage("core"');
89
+ });
90
+
91
+ it("no source files import from @fragments-sdk/cli/core as a runtime dependency", async () => {
92
+ const serverPath = resolve(viewerDir, "server.ts");
93
+ const content = await readFile(serverPath, "utf-8");
94
+
95
+ const lines = content.split("\n");
96
+ const importLines = lines.filter(
97
+ (l) => l.startsWith("import") && l.includes("@fragments-sdk/cli/core")
98
+ );
99
+ expect(importLines).toHaveLength(0);
61
100
  });
62
101
  });
63
102
 
64
103
  describe("@fragments-sdk/cli/core alias resolution", () => {
65
104
  it("core/index.ts exists at the expected path", () => {
66
- // server.ts: "@fragments-sdk/cli/core": resolve(cliPackageRoot, "src/core/index.ts")
67
105
  const corePath = resolve(cliPackageRoot, "src/core/index.ts");
68
106
  expect(existsSync(corePath)).toBe(true);
69
107
  });
@@ -71,19 +109,15 @@ describe("@fragments-sdk/cli/core alias resolution", () => {
71
109
  it("core/index.ts re-exports from @fragments-sdk/core", async () => {
72
110
  const corePath = resolve(cliPackageRoot, "src/core/index.ts");
73
111
  const content = await readFile(corePath, "utf-8");
74
- // After core extraction, cli/core is a thin re-export shim
75
112
  expect(content).toContain("@fragments-sdk/core");
76
113
  });
77
114
  });
78
115
 
79
116
  describe("virtual module @fragments-sdk/cli/core import", () => {
80
117
  it("vite-plugin generates import from @fragments-sdk/cli/core (resolved via alias)", async () => {
81
- // The virtual module template in vite-plugin.ts must import from @fragments-sdk/cli/core
82
- // which is resolved by the Vite alias to the consolidated core source
83
118
  const pluginPath = resolve(viewerDir, "vite-plugin.ts");
84
119
  const content = await readFile(pluginPath, "utf-8");
85
120
 
86
- // The generated virtual module string should reference @fragments-sdk/cli/core
87
121
  expect(content).toContain(
88
122
  'import { storyModuleToFragment, setPreviewConfig, checkStoryExclusion, isForceIncluded, isConfigExcluded } from "@fragments-sdk/cli/core"'
89
123
  );
@@ -93,43 +127,47 @@ describe("virtual module @fragments-sdk/cli/core import", () => {
93
127
  const serverPath = resolve(viewerDir, "server.ts");
94
128
  const content = await readFile(serverPath, "utf-8");
95
129
 
96
- // The alias must resolve @fragments-sdk/cli/core to the CLI's core source
97
- expect(content).toContain('"@fragments-sdk/cli/core": resolve(cliPackageRoot, "src/core/index.ts")');
130
+ expect(content).toContain(
131
+ '"@fragments-sdk/cli/core": resolve(cliPackageRoot, "src/core/index.ts")'
132
+ );
98
133
  });
99
134
 
100
135
  it("vite-plugin merges authored variant code from metadata fragments", async () => {
101
136
  const pluginPath = resolve(viewerDir, "vite-plugin.ts");
102
137
  const content = await readFile(pluginPath, "utf-8");
103
138
 
104
- expect(content).toContain("if (metaVariant.code && !fragmentVariant.code)");
139
+ expect(content).toContain(
140
+ "if (metaVariant.code && !fragmentVariant.code)"
141
+ );
105
142
  expect(content).toContain("fragmentVariant.code = metaVariant.code;");
106
143
  });
107
144
  });
108
145
 
109
146
  describe("viewer HTML templates", () => {
110
147
  it("index.html contains entry.tsx script reference", async () => {
111
- const htmlPath = resolve(viewerDir, "index.html");
148
+ const htmlPath = resolve(viewerPackageRoot, "index.html");
112
149
  const content = await readFile(htmlPath, "utf-8");
113
150
 
114
- // The HTML references /src/entry.tsx which gets rewritten to an absolute path
115
151
  expect(content).toContain('src="/src/entry.tsx"');
116
152
  });
117
153
 
118
154
  it("preview-frame.html contains preview-frame-entry.tsx reference", async () => {
119
- const htmlPath = resolve(viewerDir, "preview-frame.html");
155
+ const htmlPath = resolve(viewerSrc, "preview-frame.html");
120
156
  const content = await readFile(htmlPath, "utf-8");
121
157
 
122
158
  expect(content).toContain('src="/src/preview-frame-entry.tsx"');
123
159
  });
124
160
 
125
- it("vite-plugin rewrites entry paths using viewerAssetsRoot", async () => {
161
+ it("vite-plugin resolves viewerAssetsRoot with npm fallback", async () => {
126
162
  const pluginPath = resolve(viewerDir, "vite-plugin.ts");
127
163
  const content = await readFile(pluginPath, "utf-8");
128
164
 
129
- // The plugin must use viewerAssetsRoot (not __dirname or resolve(__dirname, ".."))
130
- // to find viewer HTML and entry files
131
- expect(content).toContain("const viewerAssetsRoot = resolve(__dirname,");
132
- expect(content).toContain('const viewerRoot = viewerAssetsRoot');
165
+ // Tries monorepo path first
166
+ expect(content).toContain("monorepoViewerSrc");
167
+ // Falls back to npm-installed
168
+ expect(content).toContain("npmViewerSrc");
169
+ // Resolves to a final viewerAssetsRoot
170
+ expect(content).toContain("const viewerAssetsRoot =");
133
171
  });
134
172
  });
135
173
 
@@ -139,7 +177,6 @@ describe("discoverInstalledFragments", () => {
139
177
  beforeAll(async () => {
140
178
  tmpDir = await mkdtemp(resolve(tmpdir(), "fragments-test-"));
141
179
 
142
- // Create a fake project with a dependency that has fragments
143
180
  await writeFile(
144
181
  resolve(tmpDir, "package.json"),
145
182
  JSON.stringify({
@@ -147,7 +184,6 @@ describe("discoverInstalledFragments", () => {
147
184
  })
148
185
  );
149
186
 
150
- // @acme/ui declares fragments
151
187
  const acmeDir = resolve(tmpDir, "node_modules/@acme/ui");
152
188
  await mkdir(resolve(acmeDir, "src/components"), { recursive: true });
153
189
  await writeFile(
@@ -163,7 +199,6 @@ describe("discoverInstalledFragments", () => {
163
199
  "export default {};"
164
200
  );
165
201
 
166
- // some-lib does NOT declare fragments
167
202
  const someLibDir = resolve(tmpDir, "node_modules/some-lib");
168
203
  await mkdir(resolve(someLibDir, "src"), { recursive: true });
169
204
  await writeFile(
@@ -209,27 +244,3 @@ describe("discoverInstalledFragments", () => {
209
244
  await rm(emptyDir, { recursive: true, force: true });
210
245
  });
211
246
  });
212
-
213
- describe("no stale @fragments/* package references", () => {
214
- it("server.ts does not reference old resolveFragmentsPackage for core", async () => {
215
- const serverPath = resolve(viewerDir, "server.ts");
216
- const content = await readFile(serverPath, "utf-8");
217
-
218
- // Should NOT try to resolve @fragments-sdk/cli/core from node_modules
219
- expect(content).not.toContain('resolveFragmentsPackage("core"');
220
- });
221
-
222
- it("no source files import from @fragments-sdk/cli/core as a runtime dependency", async () => {
223
- // All source-level imports should use relative paths (../core/).
224
- // Only the generated virtual module string uses @fragments-sdk/cli/core (resolved via alias).
225
- const serverPath = resolve(viewerDir, "server.ts");
226
- const content = await readFile(serverPath, "utf-8");
227
-
228
- // server.ts should import from relative paths, not @fragments-sdk/cli/core
229
- const lines = content.split("\n");
230
- const importLines = lines.filter(
231
- (l) => l.startsWith("import") && l.includes("@fragments-sdk/cli/core")
232
- );
233
- expect(importLines).toHaveLength(0);
234
- });
235
- });
@@ -26,16 +26,38 @@ import { loadConfig, discoverFragmentFiles, discoverInstalledFragments } from ".
26
26
  import { fragmentsPlugin } from "./vite-plugin.js";
27
27
 
28
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
29
- // At runtime, __dirname is dist/. Viewer assets live in src/viewer/.
29
+ // At runtime, __dirname is dist/.
30
30
  const cliPackageRoot = resolve(__dirname, "..");
31
- const viewerRoot = resolve(cliPackageRoot, "src/viewer");
32
31
  const packagesRoot = resolve(cliPackageRoot, "..");
33
32
  const localUiLibRoot = resolve(packagesRoot, "../libs/ui/src");
34
- const localSharedLibRoot = resolve(packagesRoot, "../libs/shared/src");
35
- const vendoredSharedLibRoot = resolve(viewerRoot, "vendor/shared/src");
36
33
  const localWebMCPRoot = resolve(packagesRoot, "webmcp/src");
37
34
  const localContextRoot = resolve(packagesRoot, "context/src");
38
35
 
36
+ /**
37
+ * Resolve the viewer package root.
38
+ * Monorepo: sibling under packages/.
39
+ * npm-installed: node_modules/@fragments-sdk/viewer.
40
+ */
41
+ function resolveViewerRoot(nodeModulesDir: string): string {
42
+ // Prefer monorepo sibling (dev mode)
43
+ const monorepoPath = resolve(packagesRoot, "viewer");
44
+ if (existsSync(join(monorepoPath, "src/shared/index.ts"))) {
45
+ return monorepoPath;
46
+ }
47
+ // Installed via npm — resolve from node_modules
48
+ const npmPath = join(nodeModulesDir, "@fragments-sdk/viewer");
49
+ if (existsSync(npmPath)) {
50
+ return npmPath;
51
+ }
52
+ // CLI's own node_modules as last resort
53
+ const cliNpmPath = join(cliPackageRoot, "node_modules/@fragments-sdk/viewer");
54
+ if (existsSync(cliNpmPath)) {
55
+ return cliNpmPath;
56
+ }
57
+ // Fallback to monorepo path (will error later with a clear message)
58
+ return monorepoPath;
59
+ }
60
+
39
61
  /**
40
62
  * Resolve the @fragments/ui alias to the correct path.
41
63
  * In external projects: use the installed @fragments-sdk/ui package.
@@ -99,21 +121,11 @@ function resolveContextLib(nodeModulesDir: string): string {
99
121
  }
100
122
 
101
123
  /**
102
- * Resolve the @fragments-sdk/shared alias to either monorepo source
103
- * or vendored viewer fallback for npm installs.
124
+ * Resolve the @fragments-sdk/viewer src directory.
104
125
  */
105
- function resolveSharedLib(): string {
106
- const localIndex = join(localSharedLibRoot, "index.ts");
107
- if (existsSync(localIndex)) {
108
- return localSharedLibRoot;
109
- }
110
-
111
- const vendoredIndex = join(vendoredSharedLibRoot, "index.ts");
112
- if (existsSync(vendoredIndex)) {
113
- return vendoredSharedLibRoot;
114
- }
115
-
116
- return localSharedLibRoot;
126
+ function resolveViewerLib(nodeModulesDir: string): string {
127
+ const viewerRoot = resolveViewerRoot(nodeModulesDir);
128
+ return resolve(viewerRoot, "src");
117
129
  }
118
130
 
119
131
  /**
@@ -383,7 +395,8 @@ export async function createDevServer(
383
395
  // Find node_modules (handles monorepo setups)
384
396
  const nodeModulesPath = findNodeModules(projectRoot);
385
397
  const uiLibRoot = resolveUiLib(nodeModulesPath);
386
- const sharedLibRoot = resolveSharedLib();
398
+ const viewerRoot = resolveViewerRoot(nodeModulesPath);
399
+ const viewerLibRoot = resolveViewerLib(nodeModulesPath);
387
400
  const webmcpLibRoot = resolveWebMCPLib(nodeModulesPath);
388
401
  const contextLibRoot = resolveContextLib(nodeModulesPath);
389
402
  // Detect whether resolved libs point to source (.ts) or dist (.js)
@@ -430,7 +443,7 @@ export async function createDevServer(
430
443
  fs: {
431
444
  // Allow serving files from viewer package, project, shared libs, node_modules root, and .storybook
432
445
  allow: [
433
- viewerRoot, uiLibRoot, sharedLibRoot, webmcpLibRoot, contextLibRoot,
446
+ viewerRoot, uiLibRoot, viewerLibRoot, webmcpLibRoot, contextLibRoot,
434
447
  projectRoot, configDir, dirname(nodeModulesPath),
435
448
  ...(hasStorybookDir ? [storybookDir] : []),
436
449
  ...installedPkgRoots,
@@ -489,8 +502,10 @@ export async function createDevServer(
489
502
  ...storybookAliases,
490
503
  // Resolve @fragments-sdk/ui to local source or installed package
491
504
  "@fragments-sdk/ui": uiLibRoot,
492
- // Resolve @fragments-sdk/shared to monorepo source or vendored fallback
493
- "@fragments-sdk/shared": sharedLibRoot,
505
+ // Resolve @fragments-sdk/viewer subpaths to viewer package source
506
+ "@fragments-sdk/viewer/shared": join(viewerLibRoot, "shared/index.ts"),
507
+ "@fragments-sdk/viewer/app": join(viewerLibRoot, "app/index.ts"),
508
+ "@fragments-sdk/viewer/docs-data": join(viewerLibRoot, "shared/docs-data/index.ts"),
494
509
  // Resolve @fragments-sdk/webmcp subpaths to monorepo source or installed package
495
510
  "@fragments-sdk/webmcp/react": isWebMCPSource
496
511
  ? join(webmcpLibRoot, "react/index.ts")
@@ -16,6 +16,7 @@
16
16
  import type { Plugin, ViteDevServer, ResolvedConfig } from "vite";
17
17
  import { resolve, dirname } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
+ import { existsSync } from "node:fs";
19
20
  import { readFile } from "node:fs/promises";
20
21
  import { transform } from "esbuild";
21
22
  import type { FragmentsConfig, CompiledFragment } from "../core/index.js";
@@ -101,8 +102,22 @@ interface CompareResponse {
101
102
  }
102
103
 
103
104
  const __dirname = dirname(fileURLToPath(import.meta.url));
104
- // At runtime, __dirname is dist/. Viewer assets live in src/viewer/.
105
- const viewerAssetsRoot = resolve(__dirname, "..", "src/viewer");
105
+ // At runtime, __dirname is dist/. Try monorepo sibling first, then npm-installed.
106
+ const monorepoViewerSrc = resolve(__dirname, "..", "..", "viewer", "src");
107
+ const npmViewerSrc = (() => {
108
+ // Check CLI's own node_modules
109
+ const cliNm = resolve(__dirname, "..", "node_modules", "@fragments-sdk", "viewer", "src");
110
+ if (existsSync(cliNm)) return cliNm;
111
+ // Walk up to find a node_modules with the viewer
112
+ let dir = resolve(__dirname, "..");
113
+ for (let i = 0; i < 5; i++) {
114
+ const candidate = resolve(dir, "node_modules", "@fragments-sdk", "viewer", "src");
115
+ if (existsSync(candidate)) return candidate;
116
+ dir = resolve(dir, "..");
117
+ }
118
+ return null;
119
+ })();
120
+ const viewerAssetsRoot = existsSync(monorepoViewerSrc) ? monorepoViewerSrc : (npmViewerSrc ?? monorepoViewerSrc);
106
121
 
107
122
  // Store pending render requests (for internal render page to pick up)
108
123
  const pendingRenders = new Map<
@@ -2138,12 +2153,13 @@ async function loadFragmentsForContext(
2138
2153
  * Serve the viewer HTML page.
2139
2154
  */
2140
2155
  async function serveViewerHTML(res: any, server: ViteDevServer): Promise<void> {
2141
- const viewerRoot = viewerAssetsRoot;
2142
- const entryPath = resolve(viewerRoot, "entry.tsx");
2156
+ const viewerSrc = viewerAssetsRoot;
2157
+ const viewerPkgRoot = resolve(viewerSrc, "..");
2158
+ const entryPath = resolve(viewerSrc, "entry.tsx");
2143
2159
 
2144
2160
  try {
2145
- // Read the viewer HTML template
2146
- let html = await readFile(resolve(viewerRoot, "index.html"), "utf-8");
2161
+ // Read the viewer HTML template (index.html is at package root, not src/)
2162
+ let html = await readFile(resolve(viewerPkgRoot, "index.html"), "utf-8");
2147
2163
 
2148
2164
  // Rewrite the entry.tsx path to use absolute path to viewer package
2149
2165
  html = html.replace("/src/entry.tsx", entryPath);
@@ -2165,12 +2181,12 @@ async function serveViewerHTML(res: any, server: ViteDevServer): Promise<void> {
2165
2181
  * This is used for iframe-based component preview with CSS isolation.
2166
2182
  */
2167
2183
  async function servePreviewFrameHTML(res: any, server: ViteDevServer): Promise<void> {
2168
- const viewerRoot = viewerAssetsRoot;
2169
- const entryPath = resolve(viewerRoot, "preview-frame-entry.tsx");
2184
+ const viewerSrc = viewerAssetsRoot;
2185
+ const entryPath = resolve(viewerSrc, "preview-frame-entry.tsx");
2170
2186
 
2171
2187
  try {
2172
2188
  // Read the preview frame HTML template
2173
- let html = await readFile(resolve(viewerRoot, "preview-frame.html"), "utf-8");
2189
+ let html = await readFile(resolve(viewerSrc, "preview-frame.html"), "utf-8");
2174
2190
 
2175
2191
  // Rewrite the entry path to use absolute path to viewer package
2176
2192
  html = html.replace("/src/preview-frame-entry.tsx", entryPath);