@scaffold-ui-kit/preview 0.0.0-stage → 2.3.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 (350) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1868 -2
  3. package/application/host/bootstrap.ts +43 -0
  4. package/application/host/main.ts +31 -0
  5. package/application/host/shell-host.ts +47 -0
  6. package/application/index.html +12 -0
  7. package/application/lib/base-url.ts +15 -0
  8. package/application/lib/no-code-block.ts +7 -0
  9. package/application/lib/no-setup.ts +3 -0
  10. package/application/lib/no-views.ts +6 -0
  11. package/application/preview/code/code.html +12 -0
  12. package/application/preview/code/main.ts +89 -0
  13. package/application/preview/globals.ts +254 -0
  14. package/application/preview/html/main.ts +93 -0
  15. package/application/preview/html/preview.html +12 -0
  16. package/application/preview/react/main.tsx +146 -0
  17. package/application/preview/react/preview.html +12 -0
  18. package/application/preview/svelte/Preview.svelte +104 -0
  19. package/application/preview/svelte/main.ts +13 -0
  20. package/application/preview/svelte/preview.html +12 -0
  21. package/application/preview/svelte/story-props.ts +172 -0
  22. package/application/preview/view/main.ts +124 -0
  23. package/application/preview/view/view.html +12 -0
  24. package/application/preview/vue/main.ts +145 -0
  25. package/application/preview/vue/preview.html +12 -0
  26. package/application/shells/charcell/components/Application/index.css +37 -0
  27. package/application/shells/charcell/components/Application/index.tsx +65 -0
  28. package/application/shells/charcell/components/Bar/index.css +68 -0
  29. package/application/shells/charcell/components/Bar/index.tsx +60 -0
  30. package/application/shells/charcell/components/CodePane/index.css +60 -0
  31. package/application/shells/charcell/components/CodePane/index.tsx +113 -0
  32. package/application/shells/charcell/components/ComponentStage/index.css +26 -0
  33. package/application/shells/charcell/components/ComponentStage/index.tsx +110 -0
  34. package/application/shells/charcell/components/Control/index.css +20 -0
  35. package/application/shells/charcell/components/Control/index.tsx +118 -0
  36. package/application/shells/charcell/components/CopyLink/index.tsx +35 -0
  37. package/application/shells/charcell/components/Description/index.css +6 -0
  38. package/application/shells/charcell/components/Description/index.tsx +24 -0
  39. package/application/shells/charcell/components/EditorAddDialog/index.css +9 -0
  40. package/application/shells/charcell/components/EditorAddDialog/index.tsx +158 -0
  41. package/application/shells/charcell/components/EditorPanel/index.css +48 -0
  42. package/application/shells/charcell/components/EditorPanel/index.tsx +496 -0
  43. package/application/shells/charcell/components/EventsRow/index.css +42 -0
  44. package/application/shells/charcell/components/EventsRow/index.tsx +68 -0
  45. package/application/shells/charcell/components/HeadingRow/index.css +11 -0
  46. package/application/shells/charcell/components/HeadingRow/index.tsx +16 -0
  47. package/application/shells/charcell/components/Hint/index.css +16 -0
  48. package/application/shells/charcell/components/Hint/index.tsx +81 -0
  49. package/application/shells/charcell/components/HtmlWarnings/index.tsx +45 -0
  50. package/application/shells/charcell/components/KeyHints/index.css +27 -0
  51. package/application/shells/charcell/components/KeyHints/index.tsx +106 -0
  52. package/application/shells/charcell/components/KitCodeBlock/index.css +55 -0
  53. package/application/shells/charcell/components/KitCodeBlock/index.tsx +18 -0
  54. package/application/shells/charcell/components/Markdown/index.tsx +16 -0
  55. package/application/shells/charcell/components/Navigation/index.css +13 -0
  56. package/application/shells/charcell/components/Navigation/index.tsx +98 -0
  57. package/application/shells/charcell/components/NavigationSplit/index.css +16 -0
  58. package/application/shells/charcell/components/NavigationSplit/index.tsx +82 -0
  59. package/application/shells/charcell/components/OptionControls/index.tsx +93 -0
  60. package/application/shells/charcell/components/PaneStatus/index.css +7 -0
  61. package/application/shells/charcell/components/PaneStatus/index.tsx +47 -0
  62. package/application/shells/charcell/components/PaneWidth/index.tsx +45 -0
  63. package/application/shells/charcell/components/PreviewGrid/index.css +39 -0
  64. package/application/shells/charcell/components/PreviewGrid/index.tsx +344 -0
  65. package/application/shells/charcell/components/PreviewPane/index.css +127 -0
  66. package/application/shells/charcell/components/PreviewPane/index.tsx +182 -0
  67. package/application/shells/charcell/components/PromptBar/index.tsx +133 -0
  68. package/application/shells/charcell/components/ScenarioRow/index.css +36 -0
  69. package/application/shells/charcell/components/ScenarioRow/index.tsx +71 -0
  70. package/application/shells/charcell/components/Select/index.tsx +48 -0
  71. package/application/shells/charcell/components/SlotOutlet/index.css +7 -0
  72. package/application/shells/charcell/components/SlotOutlet/index.tsx +25 -0
  73. package/application/shells/charcell/components/StageBar/index.css +7 -0
  74. package/application/shells/charcell/components/StageBar/index.tsx +109 -0
  75. package/application/shells/charcell/components/StageSplit/index.css +11 -0
  76. package/application/shells/charcell/components/StageSplit/index.tsx +99 -0
  77. package/application/shells/charcell/components/StatusBar/index.tsx +30 -0
  78. package/application/shells/charcell/components/ViewControls/index.css +15 -0
  79. package/application/shells/charcell/components/ViewControls/index.tsx +58 -0
  80. package/application/shells/charcell/components/ViewHeading/index.css +13 -0
  81. package/application/shells/charcell/components/ViewHeading/index.tsx +8 -0
  82. package/application/shells/charcell/components/ViewStage/index.css +9 -0
  83. package/application/shells/charcell/components/ViewStage/index.tsx +25 -0
  84. package/application/shells/charcell/components/WrapToggle/index.tsx +27 -0
  85. package/application/shells/charcell/configuration.ts +45 -0
  86. package/application/shells/charcell/controller-context.ts +27 -0
  87. package/application/shells/charcell/fit-labels.ts +42 -0
  88. package/application/shells/charcell/html-warnings.ts +18 -0
  89. package/application/shells/charcell/index.tsx +82 -0
  90. package/application/shells/charcell/page-loading.html +57 -0
  91. package/application/shells/charcell/placement.ts +24 -0
  92. package/application/shells/charcell/shell-options.ts +23 -0
  93. package/application/shells/charcell/shell.css +37 -0
  94. package/application/shells/charcell/shell.tsx +25 -0
  95. package/application/shells/charcell/split-reset.ts +47 -0
  96. package/application/shells/charcell/stacked-rows.ts +117 -0
  97. package/application/shells/charcell/stage-toggles.ts +93 -0
  98. package/application/types/manifest.d.ts +6 -0
  99. package/application/types/preview-configuration.d.ts +10 -0
  100. package/application/types/preview-shell.d.ts +7 -0
  101. package/application/types/react-registry.d.ts +15 -0
  102. package/application/types/vue-registry.d.ts +6 -0
  103. package/dist/build.d.ts +24 -0
  104. package/dist/build.js +34 -0
  105. package/dist/check.d.ts +48 -0
  106. package/dist/check.js +781 -0
  107. package/dist/cli.d.ts +2 -0
  108. package/dist/cli.js +88 -0
  109. package/dist/context.d.ts +179 -0
  110. package/dist/context.js +783 -0
  111. package/dist/controller/catalog.d.ts +339 -0
  112. package/dist/controller/catalog.js +367 -0
  113. package/dist/controller/create-preview-controller.d.ts +532 -0
  114. package/dist/controller/create-preview-controller.js +1707 -0
  115. package/dist/controller/editor-tree.d.ts +161 -0
  116. package/dist/controller/editor-tree.js +373 -0
  117. package/dist/controller/html-source.d.ts +62 -0
  118. package/dist/controller/html-source.js +61 -0
  119. package/dist/controller/index.d.ts +9 -0
  120. package/dist/controller/index.js +12 -0
  121. package/dist/controller/persistence.d.ts +191 -0
  122. package/dist/controller/persistence.js +315 -0
  123. package/dist/controller/regeneration.d.ts +30 -0
  124. package/dist/controller/regeneration.js +27 -0
  125. package/dist/controller/source.d.ts +80 -0
  126. package/dist/controller/source.js +236 -0
  127. package/dist/controller/store.d.ts +27 -0
  128. package/dist/controller/store.js +40 -0
  129. package/dist/declarations/application/shells/charcell/components/Application/index.d.ts +2 -0
  130. package/dist/declarations/application/shells/charcell/components/Bar/index.d.ts +11 -0
  131. package/dist/declarations/application/shells/charcell/components/CodePane/index.d.ts +5 -0
  132. package/dist/declarations/application/shells/charcell/components/ComponentStage/index.d.ts +2 -0
  133. package/dist/declarations/application/shells/charcell/components/Control/index.d.ts +9 -0
  134. package/dist/declarations/application/shells/charcell/components/CopyLink/index.d.ts +1 -0
  135. package/dist/declarations/application/shells/charcell/components/Description/index.d.ts +5 -0
  136. package/dist/declarations/application/shells/charcell/components/EditorAddDialog/index.d.ts +6 -0
  137. package/dist/declarations/application/shells/charcell/components/EditorPanel/index.d.ts +2 -0
  138. package/dist/declarations/application/shells/charcell/components/EventsRow/index.d.ts +4 -0
  139. package/dist/declarations/application/shells/charcell/components/HeadingRow/index.d.ts +6 -0
  140. package/dist/declarations/application/shells/charcell/components/Hint/index.d.ts +11 -0
  141. package/dist/declarations/application/shells/charcell/components/HtmlWarnings/index.d.ts +5 -0
  142. package/dist/declarations/application/shells/charcell/components/KeyHints/index.d.ts +2 -0
  143. package/dist/declarations/application/shells/charcell/components/KitCodeBlock/index.d.ts +5 -0
  144. package/dist/declarations/application/shells/charcell/components/Markdown/index.d.ts +3 -0
  145. package/dist/declarations/application/shells/charcell/components/Navigation/index.d.ts +8 -0
  146. package/dist/declarations/application/shells/charcell/components/NavigationSplit/index.d.ts +7 -0
  147. package/dist/declarations/application/shells/charcell/components/OptionControls/index.d.ts +7 -0
  148. package/dist/declarations/application/shells/charcell/components/PaneStatus/index.d.ts +2 -0
  149. package/dist/declarations/application/shells/charcell/components/PaneWidth/index.d.ts +4 -0
  150. package/dist/declarations/application/shells/charcell/components/PreviewGrid/index.d.ts +7 -0
  151. package/dist/declarations/application/shells/charcell/components/PreviewPane/index.d.ts +9 -0
  152. package/dist/declarations/application/shells/charcell/components/PromptBar/index.d.ts +1 -0
  153. package/dist/declarations/application/shells/charcell/components/ScenarioRow/index.d.ts +2 -0
  154. package/dist/declarations/application/shells/charcell/components/Select/index.d.ts +16 -0
  155. package/dist/declarations/application/shells/charcell/components/SlotOutlet/index.d.ts +5 -0
  156. package/dist/declarations/application/shells/charcell/components/StageBar/index.d.ts +2 -0
  157. package/dist/declarations/application/shells/charcell/components/StageSplit/index.d.ts +17 -0
  158. package/dist/declarations/application/shells/charcell/components/StatusBar/index.d.ts +1 -0
  159. package/dist/declarations/application/shells/charcell/components/ViewControls/index.d.ts +9 -0
  160. package/dist/declarations/application/shells/charcell/components/ViewHeading/index.d.ts +4 -0
  161. package/dist/declarations/application/shells/charcell/components/ViewStage/index.d.ts +4 -0
  162. package/dist/declarations/application/shells/charcell/components/WrapToggle/index.d.ts +1 -0
  163. package/dist/declarations/application/shells/charcell/configuration.d.ts +31 -0
  164. package/dist/declarations/application/shells/charcell/controller-context.d.ts +9 -0
  165. package/dist/declarations/application/shells/charcell/fit-labels.d.ts +18 -0
  166. package/dist/declarations/application/shells/charcell/html-warnings.d.ts +5 -0
  167. package/dist/declarations/application/shells/charcell/index.d.ts +39 -0
  168. package/dist/declarations/application/shells/charcell/placement.d.ts +7 -0
  169. package/dist/declarations/application/shells/charcell/shell-options.d.ts +15 -0
  170. package/dist/declarations/application/shells/charcell/shell.d.ts +9 -0
  171. package/dist/declarations/application/shells/charcell/split-reset.d.ts +30 -0
  172. package/dist/declarations/application/shells/charcell/stacked-rows.d.ts +58 -0
  173. package/dist/declarations/application/shells/charcell/stage-toggles.d.ts +28 -0
  174. package/dist/declarations/src/controller/catalog.d.ts +339 -0
  175. package/dist/declarations/src/controller/create-preview-controller.d.ts +532 -0
  176. package/dist/declarations/src/controller/editor-tree.d.ts +161 -0
  177. package/dist/declarations/src/controller/html-source.d.ts +62 -0
  178. package/dist/declarations/src/controller/index.d.ts +9 -0
  179. package/dist/declarations/src/controller/persistence.d.ts +191 -0
  180. package/dist/declarations/src/controller/regeneration.d.ts +30 -0
  181. package/dist/declarations/src/controller/source.d.ts +80 -0
  182. package/dist/declarations/src/controller/store.d.ts +27 -0
  183. package/dist/declarations/src/emit/common.d.ts +103 -0
  184. package/dist/declarations/src/emit/manifest.d.ts +138 -0
  185. package/dist/declarations/src/emit/source.d.ts +87 -0
  186. package/dist/declarations/src/lib/jsx-roots.d.ts +22 -0
  187. package/dist/declarations/src/lib/module-of-source.d.ts +18 -0
  188. package/dist/declarations/src/lib/naming.d.ts +29 -0
  189. package/dist/declarations/src/lib/react-spelling.d.ts +68 -0
  190. package/dist/declarations/src/lib/scenario-story.d.ts +41 -0
  191. package/dist/declarations/src/lib/slot-fill.d.ts +15 -0
  192. package/dist/declarations/src/lib/types.d.ts +741 -0
  193. package/dist/declarations/src/options.d.ts +224 -0
  194. package/dist/declarations/src/protocol.d.ts +541 -0
  195. package/dist/declarations/src/react-view.d.ts +22 -0
  196. package/dist/declarations/src/react.d.ts +17 -0
  197. package/dist/declarations/src/shell.d.ts +99 -0
  198. package/dist/declarations/src/views.d.ts +152 -0
  199. package/dist/development-server.d.ts +121 -0
  200. package/dist/development-server.js +331 -0
  201. package/dist/emit/common.d.ts +103 -0
  202. package/dist/emit/common.js +170 -0
  203. package/dist/emit/html-source.d.ts +46 -0
  204. package/dist/emit/html-source.js +322 -0
  205. package/dist/emit/manifest.d.ts +138 -0
  206. package/dist/emit/manifest.js +74 -0
  207. package/dist/emit/react.d.ts +16 -0
  208. package/dist/emit/react.js +210 -0
  209. package/dist/emit/scratch.d.ts +40 -0
  210. package/dist/emit/scratch.js +116 -0
  211. package/dist/emit/source.d.ts +87 -0
  212. package/dist/emit/source.js +789 -0
  213. package/dist/emit/svelte.d.ts +9 -0
  214. package/dist/emit/svelte.js +177 -0
  215. package/dist/emit/vue.d.ts +2 -0
  216. package/dist/emit/vue.js +152 -0
  217. package/dist/enumerate.d.ts +2 -0
  218. package/dist/enumerate.js +162 -0
  219. package/dist/generate.d.ts +43 -0
  220. package/dist/generate.js +302 -0
  221. package/dist/html-stylesheet.d.ts +43 -0
  222. package/dist/html-stylesheet.js +145 -0
  223. package/dist/index.d.ts +18 -0
  224. package/dist/index.js +21 -0
  225. package/dist/landing.d.ts +16 -0
  226. package/dist/landing.js +45 -0
  227. package/dist/lib/exported-types.d.ts +42 -0
  228. package/dist/lib/exported-types.js +86 -0
  229. package/dist/lib/html-attributes.d.ts +2 -0
  230. package/dist/lib/html-attributes.js +111 -0
  231. package/dist/lib/html-warnings.d.ts +19 -0
  232. package/dist/lib/html-warnings.js +79 -0
  233. package/dist/lib/jsx-roots.d.ts +22 -0
  234. package/dist/lib/jsx-roots.js +243 -0
  235. package/dist/lib/module-of-source.d.ts +18 -0
  236. package/dist/lib/module-of-source.js +24 -0
  237. package/dist/lib/naming.d.ts +29 -0
  238. package/dist/lib/naming.js +56 -0
  239. package/dist/lib/parse-type.d.ts +32 -0
  240. package/dist/lib/parse-type.js +103 -0
  241. package/dist/lib/react-spelling.d.ts +68 -0
  242. package/dist/lib/react-spelling.js +197 -0
  243. package/dist/lib/scenario-story.d.ts +41 -0
  244. package/dist/lib/scenario-story.js +84 -0
  245. package/dist/lib/slot-fill.d.ts +15 -0
  246. package/dist/lib/slot-fill.js +92 -0
  247. package/dist/lib/template-files.d.ts +14 -0
  248. package/dist/lib/template-files.js +25 -0
  249. package/dist/lib/types.d.ts +741 -0
  250. package/dist/lib/types.js +13 -0
  251. package/dist/lib/void-elements.d.ts +7 -0
  252. package/dist/lib/void-elements.js +13 -0
  253. package/dist/load.d.ts +37 -0
  254. package/dist/load.js +82 -0
  255. package/dist/model.d.ts +44 -0
  256. package/dist/model.js +464 -0
  257. package/dist/options.d.ts +224 -0
  258. package/dist/options.js +223 -0
  259. package/dist/prop-types.d.ts +26 -0
  260. package/dist/prop-types.js +102 -0
  261. package/dist/props-from.d.ts +84 -0
  262. package/dist/props-from.js +510 -0
  263. package/dist/protocol.d.ts +541 -0
  264. package/dist/protocol.js +819 -0
  265. package/dist/react-view.d.ts +22 -0
  266. package/dist/react-view.js +36 -0
  267. package/dist/react.d.ts +17 -0
  268. package/dist/react.js +23 -0
  269. package/dist/sass-importers.d.ts +26 -0
  270. package/dist/sass-importers.js +32 -0
  271. package/dist/shell-acceptance.d.ts +21 -0
  272. package/dist/shell-acceptance.js +37 -0
  273. package/dist/shell-page.d.ts +36 -0
  274. package/dist/shell-page.js +90 -0
  275. package/dist/shell-peers.d.ts +38 -0
  276. package/dist/shell-peers.js +61 -0
  277. package/dist/shell.d.ts +99 -0
  278. package/dist/shell.js +52 -0
  279. package/dist/shells/charcell/page.d.ts +8 -0
  280. package/dist/shells/charcell/page.js +69 -0
  281. package/dist/story-source.d.ts +113 -0
  282. package/dist/story-source.js +271 -0
  283. package/dist/story.d.ts +5 -0
  284. package/dist/story.js +28 -0
  285. package/dist/testing.d.ts +218 -0
  286. package/dist/testing.js +284 -0
  287. package/dist/views.d.ts +152 -0
  288. package/dist/views.js +7 -0
  289. package/dist/vite-configuration.d.ts +114 -0
  290. package/dist/vite-configuration.js +578 -0
  291. package/package.json +123 -3
  292. package/src/build.ts +51 -0
  293. package/src/check.ts +1062 -0
  294. package/src/cli.ts +123 -0
  295. package/src/context.ts +1168 -0
  296. package/src/controller/catalog.ts +719 -0
  297. package/src/controller/create-preview-controller.ts +2683 -0
  298. package/src/controller/editor-tree.ts +530 -0
  299. package/src/controller/html-source.ts +114 -0
  300. package/src/controller/index.ts +105 -0
  301. package/src/controller/persistence.ts +403 -0
  302. package/src/controller/regeneration.ts +55 -0
  303. package/src/controller/source.ts +333 -0
  304. package/src/controller/store.ts +55 -0
  305. package/src/development-server.ts +442 -0
  306. package/src/emit/common.ts +201 -0
  307. package/src/emit/html-source.ts +402 -0
  308. package/src/emit/manifest.ts +241 -0
  309. package/src/emit/react.ts +263 -0
  310. package/src/emit/scratch.ts +175 -0
  311. package/src/emit/source.ts +1094 -0
  312. package/src/emit/svelte.ts +230 -0
  313. package/src/emit/vue.ts +194 -0
  314. package/src/enumerate.ts +220 -0
  315. package/src/generate.ts +443 -0
  316. package/src/html-stylesheet.ts +173 -0
  317. package/src/index.ts +60 -0
  318. package/src/landing.ts +58 -0
  319. package/src/lib/exported-types.ts +114 -0
  320. package/src/lib/html-attributes.ts +113 -0
  321. package/src/lib/html-warnings.ts +81 -0
  322. package/src/lib/jsx-roots.ts +238 -0
  323. package/src/lib/module-of-source.ts +26 -0
  324. package/src/lib/naming.ts +65 -0
  325. package/src/lib/parse-type.ts +143 -0
  326. package/src/lib/react-spelling.ts +235 -0
  327. package/src/lib/scenario-story.ts +97 -0
  328. package/src/lib/slot-fill.ts +102 -0
  329. package/src/lib/template-files.ts +31 -0
  330. package/src/lib/types.ts +778 -0
  331. package/src/lib/void-elements.ts +15 -0
  332. package/src/load.ts +144 -0
  333. package/src/model.ts +568 -0
  334. package/src/options.ts +435 -0
  335. package/src/prop-types.ts +133 -0
  336. package/src/props-from.ts +678 -0
  337. package/src/protocol.ts +1135 -0
  338. package/src/react-view.ts +45 -0
  339. package/src/react.ts +34 -0
  340. package/src/sass-importers.ts +45 -0
  341. package/src/shell-acceptance.ts +45 -0
  342. package/src/shell-page.ts +133 -0
  343. package/src/shell-peers.ts +70 -0
  344. package/src/shell.ts +194 -0
  345. package/src/shells/charcell/page.ts +82 -0
  346. package/src/story-source.ts +391 -0
  347. package/src/story.ts +42 -0
  348. package/src/testing.ts +497 -0
  349. package/src/views.ts +183 -0
  350. package/src/vite-configuration.ts +675 -0
package/src/context.ts ADDED
@@ -0,0 +1,1168 @@
1
+ // Load and validate the kit configuration's `preview` block. The build leaves
2
+ // the block alone - this package owns it: the kit configuration is found and
3
+ // read by scaffold-ui-kit's own resolver (`scaffold-ui-kit.ts` or
4
+ // `scaffold-ui-kit.json`), and the block is validated by hand (the errors
5
+ // name the offending key) and resolved into absolute paths. Kit-authored TS
6
+ // modules (templates, story files, manual specs, views) are imported through
7
+ // jiti, so the built CLI compiles them on the fly as the build compiles
8
+ // templates.
9
+
10
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
11
+ import { createRequire } from 'node:module';
12
+ import { dirname, isAbsolute, resolve } from 'node:path';
13
+ import { createJiti } from 'jiti';
14
+ import type { BemOptions } from '@js-template-engine/extension-bem';
15
+ import type { TailwindOptions } from '@js-template-engine/extension-tailwind';
16
+ import type { StylesheetPlacement } from '@js-template-engine/types';
17
+ import type {
18
+ ConfigurationLookupOptions,
19
+ ResolvedKitConfiguration,
20
+ } from 'scaffold-ui-kit';
21
+ import {
22
+ ALL_TARGETS,
23
+ PREVIEW_BROWSER_KEYS,
24
+ type ModuleImporter,
25
+ type PreviewBrowserConfiguration,
26
+ type PreviewConfiguration,
27
+ type HandWrittenMapping,
28
+ type PreviewPaneWidth,
29
+ type PreviewShellSetting,
30
+ type PreviewSourceConfiguration,
31
+ type Target,
32
+ } from './lib/types.js';
33
+ import { isPaneWidth, MAXIMUM_PANE_WIDTH } from './controller/persistence.js';
34
+
35
+ export interface ResolvedSource extends PreviewSourceConfiguration {
36
+ /** Absolute (resolved against the kit root). */
37
+ templatesDirectoryAbsolute?: string;
38
+ manualModuleAbsolute?: string;
39
+ storiesDirectoryAbsolute?: string;
40
+ }
41
+
42
+ /**
43
+ * A package of a kit built into one package per target (the kit
44
+ * configuration's `packages`), other than the kit root's own.
45
+ */
46
+ export interface KitLayoutPackage {
47
+ /** The target the package is built for, or `styles` for the package of
48
+ * the kit's stylesheets. */
49
+ role: Target | 'styles';
50
+ /** The package name. */
51
+ name: string;
52
+ /** The package's directory, absolute. */
53
+ directoryAbsolute: string;
54
+ }
55
+
56
+ export interface PreviewContext {
57
+ /** The kit's root (where its package.json lives). */
58
+ kitRoot: string;
59
+ /**
60
+ * The kit's other packages, when its configuration builds it into one
61
+ * package per target (`packages`): the styles package first,
62
+ * then each target's. Absent for a kit built as one package.
63
+ */
64
+ kitPackages?: KitLayoutPackage[];
65
+ /** The kit configuration file, absolute. */
66
+ configurationPath: string;
67
+ /** The kit's own modules a `.ts` configuration loaded beside its file. */
68
+ configurationModules: string[];
69
+ /** What reading the kit configuration warns of, such as its old file
70
+ * name. */
71
+ warnings: string[];
72
+ /** The kit's name (the kit configuration's `name`). */
73
+ kitName: string;
74
+ /** The kit's version (its package.json `version`), when it has one. */
75
+ kitVersion?: string;
76
+ /**
77
+ * The scripted targets the kit builds (the kit configuration's `targets`, its
78
+ * `html` target left out), in the preview's order; all three when the kit
79
+ * names none. A component renders only in these, whatever its own targets.
80
+ */
81
+ targets: Target[];
82
+ /** Whether the kit builds html (`html` among the kit configuration's
83
+ * `targets`), so the preview shows an html pane beside the other targets'. */
84
+ buildsHtml?: boolean;
85
+ /** The mode the kit builds its Svelte target in (the kit configuration's
86
+ * `svelte.mode`), `legacy` when it names none. */
87
+ svelteMode: 'legacy' | 'runes';
88
+ /** The generated tree (`<kitRoot>/.preview-generated`). */
89
+ generatedRoot: string;
90
+ /** The static-build output (`<kitRoot>/.preview-static`). */
91
+ staticRoot: string;
92
+ /** The preview block, with `options` and `themes` defaulted to none and
93
+ * `controlHints.colorTokens`' unset choice as `null`. */
94
+ configuration: PreviewConfiguration &
95
+ Pick<PreviewBrowserConfiguration, 'options' | 'themes' | 'canvas'>;
96
+ sources: ResolvedSource[];
97
+ /** Absolute path of the kit's custom-views module, when registered. */
98
+ viewsModuleAbsolute?: string;
99
+ /** Absolute path of the kit's code-block module, when it names one. */
100
+ codeBlockModuleAbsolute?: string;
101
+ /** Absolute path of the shell module `preview.shell` names. */
102
+ shellModuleAbsolute: string;
103
+ /** Absolute path of the shell's page module, when it has one: the module
104
+ * `preview.shell.page` names, or the one its package ships. */
105
+ shellPageAbsolute?: string;
106
+ /**
107
+ * Absolute path of the shell shipped with the preview, when the kit's
108
+ * shell is it: named as it is, or through a module of the kit's that
109
+ * imports it to configure it (`charcellShell({ ... })`). Absent under a
110
+ * shell of the kit's own.
111
+ */
112
+ charcellShellAbsolute?: string;
113
+ /** The kit's `omitPattern`, compiled. */
114
+ omitPattern?: RegExp;
115
+ importModule: ModuleImporter;
116
+ /** The kit's styling settings, which the html source renders with. */
117
+ styling: KitStyling;
118
+ /**
119
+ * The kit's shared modules templates import by specifier (`add.shared`
120
+ * code modules the kit's `package.json` exports), resolved against the
121
+ * kit root. The html
122
+ * source puts their exports in scope, so an expression calling a kit helper
123
+ * evaluates.
124
+ */
125
+ sharedModulesAbsolute: string[];
126
+ /**
127
+ * The framework-free entries of `@scaffold-ui-kit/helpers` (`/runtime`
128
+ * and `/zag`) as installed for the kit, resolved from the kit root or one
129
+ * of its packages. The html source puts their exports in scope beside the
130
+ * shared modules', so an expression calling a helper evaluates. Empty when
131
+ * the kit does not have the package.
132
+ */
133
+ helperModulesAbsolute: string[];
134
+ /**
135
+ * The kit's shared modules with the package path its built files import
136
+ * each by (the one the kit's `package.json` exports give it, or an older
137
+ * configuration's `specifier`), the source relative to the kit root as
138
+ * written.
139
+ */
140
+ sharedSpecifiers: { source: string; specifier: string }[];
141
+ /**
142
+ * The kit's Sass load paths (the kit configuration's top-level
143
+ * `loadPaths`), resolved against the kit root, which the kit's build
144
+ * compiles its SCSS with.
145
+ */
146
+ sassLoadPathsAbsolute: string[];
147
+ /**
148
+ * The parts of each compound whose root has no template, such as a
149
+ * hand-written component (the kit configuration's `parts`), by root name.
150
+ */
151
+ parts: Record<string, string[]>;
152
+ }
153
+
154
+ /** The styling keys of the kit configuration the html source reads. */
155
+ export interface KitStyling {
156
+ /** The styling extensions, in configuration order. */
157
+ extensions: Array<'bem' | 'tailwind'>;
158
+ /** Where each component's stylesheet is placed (the kit configuration's
159
+ * `stylesheetPlacement`), `in-file` when it names none. */
160
+ placement: StylesheetPlacement;
161
+ /** The BEM extension's options (the kit configuration's `bem` block). */
162
+ bem?: BemOptions;
163
+ /** The Tailwind extension's options (the kit configuration's `tailwind`
164
+ * block). */
165
+ tailwind?: TailwindOptions;
166
+ }
167
+
168
+ function fail(message: string): never {
169
+ throw new Error(`[preview] ${message}`);
170
+ }
171
+
172
+ /** The keys the `preview` block takes. */
173
+ const PREVIEW_KEYS = [
174
+ 'shell',
175
+ 'sources',
176
+ 'options',
177
+ 'canvas',
178
+ 'themes',
179
+ 'mount',
180
+ 'viewCanvas',
181
+ 'paneWidths',
182
+ 'controlHints',
183
+ 'labels',
184
+ 'viewsModule',
185
+ 'codeBlock',
186
+ 'setupModules',
187
+ 'htmlStylesheets',
188
+ 'aliases',
189
+ 'scssLoadPaths',
190
+ 'optimizeDepsExclude',
191
+ 'dedupe',
192
+ 'omitPattern',
193
+ 'handWrittenComponents',
194
+ 'port',
195
+ 'host',
196
+ 'persistence',
197
+ 'landing',
198
+ ] as const satisfies readonly (keyof PreviewConfiguration)[];
199
+
200
+ /** The keys a `preview.sources` entry takes. */
201
+ const SOURCE_KEYS = [
202
+ 'id',
203
+ 'label',
204
+ 'order',
205
+ 'moduleBase',
206
+ 'templatesDirectory',
207
+ 'manualModule',
208
+ 'storiesDirectory',
209
+ ] as const satisfies readonly (keyof PreviewSourceConfiguration)[];
210
+
211
+ /** The keys `preview.codeBlock` takes. */
212
+ const CODE_BLOCK_KEYS = [
213
+ 'module',
214
+ 'framework',
215
+ ] as const satisfies readonly (keyof NonNullable<
216
+ PreviewConfiguration['codeBlock']
217
+ >)[];
218
+
219
+ /** The keys `preview.landing` takes. */
220
+ const LANDING_KEYS = ['view', 'component', 'scenario'] as const;
221
+
222
+ /** The keys the object form of a `handWrittenComponents` entry takes. */
223
+ const HAND_WRITTEN_KEYS = [
224
+ 'template',
225
+ 'slots',
226
+ ] as const satisfies readonly (keyof Exclude<HandWrittenMapping, string>)[];
227
+
228
+ /** What to write for a key the `preview` block does not take, where one thing is meant. */
229
+ const PREVIEW_KEY_REPLACEMENTS: ReadonlyMap<string, string> = new Map([
230
+ ['scopes', "'sources'"],
231
+ [
232
+ 'configurationModule',
233
+ "the keys of the 'preview' block, such as options, canvas, themes and mount",
234
+ ],
235
+ ]);
236
+
237
+ /** What to write for a key a `preview.sources` entry does not take, where one thing is meant. */
238
+ const SOURCE_KEY_REPLACEMENTS: ReadonlyMap<string, string> = new Map([
239
+ ['sidecarsDirectory', "'storiesDirectory'"],
240
+ ]);
241
+
242
+ /** Whether a value is a plain object, whose keys can be checked. */
243
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
244
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
245
+ }
246
+
247
+ /**
248
+ * Each key of an object that is not among the keys it takes, with its path
249
+ * and the keys the object takes, as the scaffolder reports the
250
+ * configuration's own keys, or what to use instead where `replacements`
251
+ * names it.
252
+ */
253
+ function unknownKeyProblems(
254
+ value: object,
255
+ allowed: readonly string[],
256
+ path: string,
257
+ description: string,
258
+ replacements: ReadonlyMap<string, string> = new Map()
259
+ ): string[] {
260
+ return Object.keys(value)
261
+ .filter((key) => !allowed.includes(key))
262
+ .map((key) => {
263
+ const replacement = replacements.get(key);
264
+ return `unknown key '${path}.${key}'; ${
265
+ replacement === undefined
266
+ ? `${description} takes ${allowed.join(', ')}`
267
+ : `use ${replacement}`
268
+ }`;
269
+ });
270
+ }
271
+
272
+ /**
273
+ * What is wrong with a list of pane widths, or nothing: it must be a list of
274
+ * `{ label, width }`, each label a non-empty string and each width a whole
275
+ * number of CSS pixels from 1 to the most a pane is laid out at, no label
276
+ * and no width named twice.
277
+ *
278
+ * @param paneWidths - The list, as the kit gives it.
279
+ * @param name - Where the kit gives it, for the message.
280
+ * @returns The problem, or `undefined` when the list is sound.
281
+ * @example
282
+ * ```ts
283
+ * paneWidthsProblem([{ label: 'small', width: 375 }], 'paneWidths'); // undefined
284
+ * ```
285
+ */
286
+ export function paneWidthsProblem(
287
+ paneWidths: unknown,
288
+ name: string
289
+ ): string | undefined {
290
+ if (
291
+ !Array.isArray(paneWidths) ||
292
+ paneWidths.some(
293
+ (paneWidth: unknown) =>
294
+ !isPlainObject(paneWidth) ||
295
+ typeof paneWidth.label !== 'string' ||
296
+ paneWidth.label.trim() === '' ||
297
+ !isPaneWidth(paneWidth.width)
298
+ )
299
+ )
300
+ return `${name} must be a list of { label, width }: a label to pick it by, and a width in CSS pixels, a whole number from 1 to ${MAXIMUM_PANE_WIDTH}`;
301
+ const widths = paneWidths as PreviewPaneWidth[];
302
+ if (
303
+ new Set(widths.map((paneWidth) => paneWidth.label)).size !== widths.length
304
+ )
305
+ return `${name} must not name a label twice`;
306
+ if (
307
+ new Set(widths.map((paneWidth) => paneWidth.width)).size !== widths.length
308
+ )
309
+ return `${name} must not name a width twice`;
310
+ return undefined;
311
+ }
312
+
313
+ /** What is wrong with the shape of `preview.landing`, or nothing. */
314
+ function landingShapeProblem(landing: unknown): string | undefined {
315
+ const shape =
316
+ "preview.landing must be { view } or { component, scenario? }: a view's id, or a story's id and, if it is not to open on its baseline, a scenario's, as the preview's address names them";
317
+ if (!isPlainObject(landing)) return shape;
318
+ const candidate: Record<string, unknown> = landing;
319
+ const named = (key: (typeof LANDING_KEYS)[number]) =>
320
+ typeof candidate[key] === 'string' && candidate[key] !== '';
321
+ if ('view' in candidate) {
322
+ if (!named('view') || 'component' in candidate || 'scenario' in candidate)
323
+ return shape;
324
+ return undefined;
325
+ }
326
+ if (!named('component')) return shape;
327
+ if ('scenario' in candidate && !named('scenario')) return shape;
328
+ return undefined;
329
+ }
330
+
331
+ function validateSource(
332
+ source: unknown,
333
+ index: number
334
+ ): PreviewSourceConfiguration {
335
+ const candidate = source as Partial<PreviewSourceConfiguration>;
336
+ if (!candidate || typeof candidate !== 'object')
337
+ fail(`preview.sources[${index}] must be an object`);
338
+ for (const key of ['id', 'label', 'moduleBase'] as const) {
339
+ if (typeof candidate[key] !== 'string' || !candidate[key]) {
340
+ fail(`preview.sources[${index}].${key} must be a non-empty string`);
341
+ }
342
+ }
343
+ if (typeof candidate.order !== 'number')
344
+ fail(`preview.sources[${index}].order must be a number`);
345
+ return candidate as PreviewSourceConfiguration;
346
+ }
347
+
348
+ /**
349
+ * What is wrong with a `preview.shell` that is set, or `undefined` when it
350
+ * is a specifier or a `{ module, page }` of specifiers.
351
+ */
352
+ function shellSettingProblem(setting: unknown): string | undefined {
353
+ if (typeof setting === 'string') return undefined;
354
+ const shape =
355
+ "preview.shell must be a package specifier or a module path, or an object with 'module' and optional 'page'";
356
+ if (typeof setting !== 'object' || setting === null || Array.isArray(setting))
357
+ return shape;
358
+ const { module, page, ...rest } = setting as Record<string, unknown>;
359
+ const unknown = Object.keys(rest);
360
+ if (unknown.length > 0)
361
+ return `unknown key 'preview.shell.${unknown[0]}'; preview.shell takes module and page`;
362
+ if (typeof module !== 'string' || !module)
363
+ return "preview.shell.module is required: the shell's module, as a package specifier or a module path relative to the kit root";
364
+ if (page !== undefined && (typeof page !== 'string' || !page))
365
+ return "preview.shell.page must be a package specifier or a module path relative to the kit root: the shell's page module";
366
+ return undefined;
367
+ }
368
+
369
+ /** The shell shipped with the preview, named when a kit configures none. */
370
+ export const CHARCELL_SHELL = '@scaffold-ui-kit/preview/shells/charcell';
371
+
372
+ /**
373
+ * Where `preview.shell` points: a module in the kit when it is
374
+ * written as a path (relative, absolute, or one that exists under the kit
375
+ * root, like the other module settings), else a package resolved from the
376
+ * kit's root the way the kit's own imports are.
377
+ */
378
+ function resolveShellModule(kitRoot: string, shell: string): string {
379
+ if (isModulePath(kitRoot, shell)) {
380
+ const inKit = resolve(kitRoot, shell);
381
+ if (!existsSync(inKit)) {
382
+ fail(`preview.shell names a module that does not exist: ${inKit}`);
383
+ }
384
+ return inKit;
385
+ }
386
+ try {
387
+ return createRequire(resolve(kitRoot, 'package.json')).resolve(shell);
388
+ } catch {
389
+ return fail(
390
+ `preview.shell '${shell}' is neither a module in the kit nor a package installed in it (resolved from ${kitRoot}). ` +
391
+ `Install the package the shell comes from, or give a path relative to the kit root. ` +
392
+ `The shell shipped with the preview is '${CHARCELL_SHELL}'; it needs charcell and @zag-js/react installed.`
393
+ );
394
+ }
395
+ }
396
+
397
+ /** Whether a module setting is written as a path: relative, absolute, or
398
+ * one that exists under the kit root. Anything else is a package specifier. */
399
+ function isModulePath(kitRoot: string, setting: string): boolean {
400
+ return (
401
+ setting.startsWith('.') ||
402
+ isAbsolute(setting) ||
403
+ existsSync(resolve(kitRoot, setting))
404
+ );
405
+ }
406
+
407
+ /**
408
+ * Whether the package a specifier names lists `<specifier>/<subpath>` among
409
+ * its exports, read from its `package.json`; `false` when the package or its
410
+ * exports cannot be read.
411
+ */
412
+ function packageExports(
413
+ fromKit: NodeJS.Require,
414
+ specifier: string,
415
+ subpath: string
416
+ ): boolean {
417
+ const [scopeOrName, name] = specifier.split('/');
418
+ const packageName = specifier.startsWith('@')
419
+ ? `${scopeOrName}/${name}`
420
+ : scopeOrName!;
421
+ try {
422
+ const manifest = fromKit(`${packageName}/package.json`) as {
423
+ exports?: unknown;
424
+ };
425
+ const key = `.${specifier.slice(packageName.length)}/${subpath}`;
426
+ return (
427
+ typeof manifest.exports === 'object' &&
428
+ manifest.exports !== null &&
429
+ key in manifest.exports
430
+ );
431
+ } catch {
432
+ return false;
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Where the shell's page module is: the one `preview.shell.page` names, a
438
+ * path in the kit or a package resolved from the kit's root, as the shell
439
+ * itself is; or, with none named, the module at the shell's specifier
440
+ * followed by `/page`, when the shell is named as a package specifier and
441
+ * its package has one. A shell with no page has none, and the host page
442
+ * starts empty.
443
+ *
444
+ * @param kitRoot - The kit's root directory.
445
+ * @param shell - The shell module, as `preview.shell` names it.
446
+ * @param page - The page module, as `preview.shell.page` names it.
447
+ * @returns The page module's absolute path, or `undefined` with no page.
448
+ */
449
+ function resolveShellPage(
450
+ kitRoot: string,
451
+ shell: string,
452
+ page: string | undefined
453
+ ): string | undefined {
454
+ const fromKit = createRequire(resolve(kitRoot, 'package.json'));
455
+ if (page === undefined) {
456
+ if (isModulePath(kitRoot, shell)) return undefined;
457
+ try {
458
+ return fromKit.resolve(`${shell}/page`);
459
+ } catch (error) {
460
+ // A package that exports no page has none; one whose export cannot
461
+ // be loaded is a mistake worth naming.
462
+ const code = (error as NodeJS.ErrnoException).code;
463
+ if (
464
+ code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' ||
465
+ (code === 'MODULE_NOT_FOUND' && !packageExports(fromKit, shell, 'page'))
466
+ ) {
467
+ return undefined;
468
+ }
469
+ return fail(
470
+ `the shell '${shell}' exports a page at '${shell}/page' that does not resolve: ${(error as Error).message}`
471
+ );
472
+ }
473
+ }
474
+ if (isModulePath(kitRoot, page)) {
475
+ const inKit = resolve(kitRoot, page);
476
+ if (!existsSync(inKit)) {
477
+ fail(`preview.shell.page names a module that does not exist: ${inKit}`);
478
+ }
479
+ return inKit;
480
+ }
481
+ try {
482
+ return fromKit.resolve(page);
483
+ } catch {
484
+ return fail(
485
+ `preview.shell.page '${page}' is neither a module in the kit nor a package installed in it (resolved from ${kitRoot}). ` +
486
+ `Give a path relative to the kit root, or the page module of an installed shell, such as '${CHARCELL_SHELL}/page'.`
487
+ );
488
+ }
489
+ }
490
+
491
+ /** Whether a module's source imports the shell shipped with the preview. */
492
+ const IMPORTS_CHARCELL_SHELL = new RegExp(
493
+ `(?:from\\s*|import\\s*\\(\\s*|require\\s*\\(\\s*)['"]${CHARCELL_SHELL.replace(/\./g, '\\.')}['"]`
494
+ );
495
+
496
+ /**
497
+ * Where the shell shipped with the preview is, when the kit's shell is it:
498
+ * the shell module itself when `preview.shell` names it, or the module it
499
+ * resolves to from a module of the kit's that imports it to configure it.
500
+ * `undefined` under a shell of the kit's own, or when the kit's module
501
+ * imports it from a package the kit has not installed.
502
+ */
503
+ function resolveCharcellShell(
504
+ shell: string,
505
+ shellModuleAbsolute: string
506
+ ): string | undefined {
507
+ if (shell === CHARCELL_SHELL) return shellModuleAbsolute;
508
+ let source: string;
509
+ try {
510
+ source = readFileSync(shellModuleAbsolute, 'utf8');
511
+ } catch {
512
+ return undefined;
513
+ }
514
+ if (!IMPORTS_CHARCELL_SHELL.test(source)) return undefined;
515
+ try {
516
+ return createRequire(shellModuleAbsolute).resolve(CHARCELL_SHELL);
517
+ } catch {
518
+ return undefined;
519
+ }
520
+ }
521
+
522
+ function readPackageVersion(kitRoot: string): string | undefined {
523
+ const packagePath = resolve(kitRoot, 'package.json');
524
+ if (!existsSync(packagePath)) return undefined;
525
+ // A package.json that is not JSON has no version to read; `check` reports it.
526
+ let manifest: { version?: unknown } | null;
527
+ try {
528
+ manifest = JSON.parse(readFileSync(packagePath, 'utf8')) as {
529
+ version?: unknown;
530
+ } | null;
531
+ } catch {
532
+ return undefined;
533
+ }
534
+ const version = manifest?.version;
535
+ return typeof version === 'string' ? version : undefined;
536
+ }
537
+
538
+ /** The kit configuration's keys the preview reads beside its block. */
539
+ type KitConfigurationRead = Pick<KitStyling, 'bem' | 'tailwind'> & {
540
+ name?: string;
541
+ stylesheetPlacement?: StylesheetPlacement;
542
+ targets?: unknown;
543
+ styling?: Array<'bem' | 'tailwind'>;
544
+ preview?: unknown;
545
+ add?: { shared?: Array<{ source?: unknown; specifier?: unknown }> };
546
+ packages?: unknown;
547
+ loadPaths?: unknown;
548
+ svelte?: { mode?: unknown };
549
+ parts?: unknown;
550
+ };
551
+
552
+ /**
553
+ * The kit configuration's `parts`, each root's list of part names; what is
554
+ * not a list of names is left out, as the kit's build reports it.
555
+ */
556
+ function partsOf(value: unknown): Record<string, string[]> {
557
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
558
+ return {};
559
+ const parts: Record<string, string[]> = {};
560
+ for (const [root, rootParts] of Object.entries(value)) {
561
+ if (Array.isArray(rootParts))
562
+ parts[root] = rootParts.filter(
563
+ (part): part is string => typeof part === 'string' && part !== ''
564
+ );
565
+ }
566
+ return parts;
567
+ }
568
+
569
+ /** scaffold-ui-kit, loaded as the CommonJS package it is. */
570
+ function scaffoldUiKit(): typeof import('scaffold-ui-kit') {
571
+ return createRequire(import.meta.url)(
572
+ 'scaffold-ui-kit'
573
+ ) as typeof import('scaffold-ui-kit');
574
+ }
575
+
576
+ /** The resolver and module loader of scaffold-ui-kit the preview reads a
577
+ * kit's configuration with. */
578
+ type KitResolver = Pick<
579
+ typeof import('scaffold-ui-kit'),
580
+ 'resolveKitConfiguration'
581
+ >;
582
+
583
+ /**
584
+ * The scaffold-ui-kit the kit has installed, when it has the resolver, so
585
+ * the preview reads the configuration as the kit's own build does; else the
586
+ * preview's own copy.
587
+ */
588
+ function kitResolver(kitRoot: string): KitResolver {
589
+ const own = scaffoldUiKit();
590
+ let installed: Partial<KitResolver> | undefined;
591
+ try {
592
+ installed = createRequire(resolve(kitRoot, 'package.json'))(
593
+ 'scaffold-ui-kit'
594
+ ) as Partial<KitResolver>;
595
+ } catch {
596
+ return own;
597
+ }
598
+ return typeof installed.resolveKitConfiguration === 'function'
599
+ ? (installed as KitResolver)
600
+ : own;
601
+ }
602
+
603
+ /**
604
+ * The problems that keep a value from reaching the browser as JSON, each
605
+ * naming where in the value it sits: a function, `undefined` in a list, a
606
+ * number JSON has no form for, and an object other than a plain one or a
607
+ * list (a regular expression, a date, a map).
608
+ *
609
+ * @param value - The value.
610
+ * @param path - Where the value sits, as `preview.options`.
611
+ * @returns One message per problem.
612
+ * @example
613
+ * ```ts
614
+ * jsonProblems({ apply: () => {} }, 'preview.options[0]');
615
+ * // ["preview.options[0].apply is a function, which can't be sent to the browser as JSON"]
616
+ * ```
617
+ */
618
+ export function jsonProblems(
619
+ value: unknown,
620
+ path: string,
621
+ ancestors: WeakMap<object, string> = new WeakMap()
622
+ ): string[] {
623
+ const unsendable = (what: string) => [
624
+ `${path} is ${what}, which can't be sent to the browser as JSON`,
625
+ ];
626
+ if (value === null) return [];
627
+ switch (typeof value) {
628
+ case 'string':
629
+ case 'boolean':
630
+ return [];
631
+ case 'number':
632
+ return Number.isFinite(value) ? [] : unsendable(String(value));
633
+ case 'undefined':
634
+ return unsendable('undefined');
635
+ case 'function':
636
+ return unsendable('a function');
637
+ case 'bigint':
638
+ case 'symbol':
639
+ return unsendable(`a ${typeof value}`);
640
+ }
641
+ const object = value as object;
642
+ // An object inside itself has no end to write out.
643
+ const outer = ancestors.get(object);
644
+ if (outer !== undefined)
645
+ return [
646
+ `${path} is ${outer} again, a cycle that can't be sent to the browser as JSON`,
647
+ ];
648
+ const prototype = Object.getPrototypeOf(object);
649
+ if (
650
+ !Array.isArray(object) &&
651
+ prototype !== Object.prototype &&
652
+ prototype !== null
653
+ )
654
+ return unsendable(`${object.constructor?.name ?? 'an object'} object`);
655
+ ancestors.set(object, path);
656
+ const problems = Array.isArray(object)
657
+ ? object.flatMap((entry, index) =>
658
+ jsonProblems(entry, `${path}[${index}]`, ancestors)
659
+ )
660
+ : Object.entries(object as Record<string, unknown>)
661
+ .filter(([, entry]) => entry !== undefined)
662
+ .flatMap(([key, entry]) =>
663
+ jsonProblems(entry, `${path}.${key}`, ancestors)
664
+ );
665
+ // Only an ancestor makes a cycle; the same object twice side by side is fine.
666
+ ancestors.delete(object);
667
+ return problems;
668
+ }
669
+
670
+ /**
671
+ * Finds the kit configuration (`--configuration`, the kit's `package.json`
672
+ * pointer, or `scaffold-ui-kit.ts` or `scaffold-ui-kit.json` at the kit
673
+ * root) and resolves the preview context from its `preview` block.
674
+ *
675
+ * @param kitRoot - The kit root.
676
+ * @param options - The configuration file a command was given, if any.
677
+ * @returns The context.
678
+ */
679
+ export function loadPreviewContext(
680
+ kitRoot: string,
681
+ options: ConfigurationLookupOptions = {}
682
+ ): PreviewContext {
683
+ const resolver = kitResolver(kitRoot);
684
+ let resolved: ResolvedKitConfiguration;
685
+ try {
686
+ resolved = resolver.resolveKitConfiguration(kitRoot, options);
687
+ } catch (error) {
688
+ fail(error instanceof Error ? error.message : String(error));
689
+ }
690
+ const configurationPath = resolved.file.path;
691
+ const configurationName = resolved.file.name;
692
+ const warnings = [...resolved.warnings];
693
+ const configurationModules = new Set(resolved.modules ?? []);
694
+ const kitConfiguration = resolved.configuration as KitConfigurationRead;
695
+
696
+ const authoredBlock: unknown = kitConfiguration.preview;
697
+ if (!isPlainObject(authoredBlock)) {
698
+ fail(
699
+ `${configurationName} has no preview block${
700
+ 'extensions' in (kitConfiguration as object)
701
+ ? "; the preview's settings go in a top-level 'preview' block, not 'extensions'"
702
+ : ''
703
+ }`
704
+ );
705
+ }
706
+ const authored = authoredBlock as unknown as PreviewConfiguration;
707
+ // A misspelled key would otherwise go unread without a word: every unknown
708
+ // key fails, the block's and each object's inside it, all named at once.
709
+ const unknownKeys = [
710
+ ...unknownKeyProblems(
711
+ authored,
712
+ PREVIEW_KEYS,
713
+ 'preview',
714
+ "the 'preview' block",
715
+ PREVIEW_KEY_REPLACEMENTS
716
+ ),
717
+ ...(Array.isArray(authored.sources) ? authored.sources : []).flatMap(
718
+ (source: unknown, index) =>
719
+ source && typeof source === 'object' && !Array.isArray(source)
720
+ ? unknownKeyProblems(
721
+ source,
722
+ SOURCE_KEYS,
723
+ `preview.sources[${index}]`,
724
+ 'a source',
725
+ SOURCE_KEY_REPLACEMENTS
726
+ )
727
+ : []
728
+ ),
729
+ ...(isPlainObject(authored.codeBlock)
730
+ ? unknownKeyProblems(
731
+ authored.codeBlock,
732
+ CODE_BLOCK_KEYS,
733
+ 'preview.codeBlock',
734
+ 'a code block'
735
+ )
736
+ : []),
737
+ ...(isPlainObject(authored.setupModules)
738
+ ? unknownKeyProblems(
739
+ authored.setupModules,
740
+ ALL_TARGETS,
741
+ 'preview.setupModules',
742
+ "the 'setupModules' map"
743
+ )
744
+ : []),
745
+ ...(isPlainObject(authored.landing)
746
+ ? unknownKeyProblems(
747
+ authored.landing,
748
+ LANDING_KEYS,
749
+ 'preview.landing',
750
+ 'the landing entry'
751
+ )
752
+ : []),
753
+ ...(isPlainObject(authored.handWrittenComponents)
754
+ ? Object.entries(authored.handWrittenComponents).flatMap(
755
+ ([name, mapping]: [string, unknown]) =>
756
+ isPlainObject(mapping)
757
+ ? unknownKeyProblems(
758
+ mapping,
759
+ HAND_WRITTEN_KEYS,
760
+ `preview.handWrittenComponents.${name}`,
761
+ 'a hand-written component'
762
+ )
763
+ : []
764
+ )
765
+ : []),
766
+ ];
767
+ if (unknownKeys.length > 0) fail(unknownKeys.join('\n[preview] '));
768
+ if (!Array.isArray(authored.sources) || authored.sources.length === 0) {
769
+ fail('preview.sources must be a non-empty array');
770
+ }
771
+ authored.sources.forEach(validateSource);
772
+ const shellSetting = authored.shell as unknown;
773
+ if (shellSetting === undefined || shellSetting === '') {
774
+ fail(
775
+ `preview.shell is required: the shell that renders the preview's interface, as a package specifier or a module path relative to the kit root. ` +
776
+ `To use the shell shipped with the preview, install charcell and @zag-js/react and set shell: '${CHARCELL_SHELL}'.`
777
+ );
778
+ }
779
+ const shellProblem = shellSettingProblem(shellSetting);
780
+ if (shellProblem !== undefined) fail(shellProblem);
781
+ const shell =
782
+ typeof shellSetting === 'string'
783
+ ? shellSetting
784
+ : (shellSetting as PreviewShellSetting).module;
785
+ const shellPage =
786
+ typeof shellSetting === 'string'
787
+ ? undefined
788
+ : (shellSetting as PreviewShellSetting).page;
789
+ const canvas = authored.canvas as unknown;
790
+ if (
791
+ canvas !== undefined &&
792
+ (!isPlainObject(canvas) ||
793
+ typeof canvas.namePrefix !== 'string' ||
794
+ canvas.namePrefix === '')
795
+ ) {
796
+ fail(
797
+ "preview.canvas must be { namePrefix, pageBackground?, extraCss? }: namePrefix the prefix for the names the preview sets in each frame, such as 'my-ui-kit'"
798
+ );
799
+ }
800
+ for (const key of ['options', 'themes'] as const) {
801
+ if (authored[key] !== undefined && !Array.isArray(authored[key]))
802
+ fail(`preview.${key} must be a list`);
803
+ }
804
+ for (const key of [
805
+ 'mount',
806
+ 'viewCanvas',
807
+ 'controlHints',
808
+ 'labels',
809
+ ] as const) {
810
+ if (authored[key] !== undefined && !isPlainObject(authored[key]))
811
+ fail(`preview.${key} must be an object`);
812
+ }
813
+ // What the preview's pages read reaches them as JSON, so it must be JSON.
814
+ const unsendable = PREVIEW_BROWSER_KEYS.filter(
815
+ (key) => authored[key] !== undefined
816
+ ).flatMap((key) => jsonProblems(authored[key], `preview.${key}`));
817
+ if (unsendable.length > 0) fail(unsendable.join('\n[preview] '));
818
+ const colorTokens = authored.controlHints?.colorTokens;
819
+ if (colorTokens !== undefined && !Array.isArray(colorTokens))
820
+ fail('preview.controlHints.colorTokens must be a list of tokens');
821
+ const configuration: PreviewContext['configuration'] = {
822
+ ...authored,
823
+ options: authored.options ?? [],
824
+ themes: authored.themes ?? [],
825
+ // The kit's name, made safe for the names it prefixes, by default.
826
+ canvas: authored.canvas ?? {
827
+ namePrefix: (kitConfiguration.name ?? 'kit')
828
+ .replace(/[^A-Za-z0-9_-]/g, '-')
829
+ .replace(/^-+|-+$/g, ''),
830
+ },
831
+ shell,
832
+ ...(authored.controlHints
833
+ ? {
834
+ controlHints: {
835
+ ...authored.controlHints,
836
+ ...(colorTokens
837
+ ? { colorTokens: colorTokens.map((token) => token ?? null) }
838
+ : {}),
839
+ },
840
+ }
841
+ : {}),
842
+ };
843
+ let omitPattern: RegExp | undefined;
844
+ if (configuration.omitPattern !== undefined) {
845
+ try {
846
+ omitPattern = new RegExp(configuration.omitPattern);
847
+ } catch {
848
+ fail(
849
+ `preview.omitPattern must be a regular expression: ${String(configuration.omitPattern)}`
850
+ );
851
+ }
852
+ }
853
+
854
+ const handWritten = configuration.handWrittenComponents;
855
+ if (
856
+ handWritten !== undefined &&
857
+ (typeof handWritten !== 'object' ||
858
+ handWritten === null ||
859
+ Array.isArray(handWritten) ||
860
+ Object.values(handWritten).some(
861
+ (value) =>
862
+ typeof value !== 'string' &&
863
+ (typeof value !== 'object' ||
864
+ value === null ||
865
+ typeof value.template !== 'string' ||
866
+ (value.slots !== undefined &&
867
+ (typeof value.slots !== 'object' ||
868
+ Object.values(value.slots).some(
869
+ (slot) => typeof slot !== 'string'
870
+ ))))
871
+ ))
872
+ ) {
873
+ fail(
874
+ "preview.handWrittenComponents must map each component name to 'content', a template name, or { template, slots }"
875
+ );
876
+ }
877
+
878
+ const dedupe = configuration.dedupe;
879
+ if (
880
+ dedupe !== undefined &&
881
+ (!Array.isArray(dedupe) ||
882
+ dedupe.some((name) => typeof name !== 'string' || name === ''))
883
+ ) {
884
+ fail('preview.dedupe must be a list of package names');
885
+ }
886
+
887
+ const htmlStylesheets = configuration.htmlStylesheets;
888
+ if (
889
+ htmlStylesheets !== undefined &&
890
+ (!Array.isArray(htmlStylesheets) ||
891
+ htmlStylesheets.some((path) => typeof path !== 'string' || path === ''))
892
+ ) {
893
+ fail(
894
+ 'preview.htmlStylesheets must be a list of stylesheet paths, relative to the kit root'
895
+ );
896
+ }
897
+
898
+ const codeBlock = configuration.codeBlock;
899
+ if (
900
+ codeBlock !== undefined &&
901
+ (typeof codeBlock !== 'object' ||
902
+ codeBlock === null ||
903
+ typeof codeBlock.module !== 'string' ||
904
+ codeBlock.module === '' ||
905
+ (codeBlock.framework !== undefined &&
906
+ !['react', 'vue', 'svelte', 'dom'].includes(codeBlock.framework)))
907
+ ) {
908
+ fail(
909
+ "preview.codeBlock must be { module, framework? }: a module path relative to the kit root, and 'react', 'vue', 'svelte' or 'dom'"
910
+ );
911
+ }
912
+
913
+ const persistence = configuration.persistence;
914
+ if (
915
+ persistence !== undefined &&
916
+ persistence !== 'url' &&
917
+ persistence !== 'url-and-local-storage'
918
+ ) {
919
+ fail(
920
+ `preview.persistence must be 'url' or 'url-and-local-storage', not ${JSON.stringify(persistence)}`
921
+ );
922
+ }
923
+
924
+ if (configuration.landing !== undefined) {
925
+ const problem = landingShapeProblem(configuration.landing);
926
+ if (problem) fail(problem);
927
+ }
928
+
929
+ const sources: ResolvedSource[] = [...configuration.sources]
930
+ .sort((a, b) => a.order - b.order)
931
+ .map((source) => {
932
+ const templatesDirectoryAbsolute = source.templatesDirectory
933
+ ? resolve(kitRoot, source.templatesDirectory)
934
+ : undefined;
935
+ const storiesDirectoryAbsolute = source.storiesDirectory
936
+ ? resolve(kitRoot, source.storiesDirectory)
937
+ : undefined;
938
+ return {
939
+ ...source,
940
+ templatesDirectoryAbsolute,
941
+ manualModuleAbsolute: source.manualModule
942
+ ? resolve(kitRoot, source.manualModule)
943
+ : undefined,
944
+ storiesDirectoryAbsolute,
945
+ };
946
+ });
947
+
948
+ const kitTargets = kitConfiguration.targets;
949
+ if (
950
+ kitTargets !== undefined &&
951
+ (!Array.isArray(kitTargets) ||
952
+ kitTargets.some((target) => typeof target !== 'string'))
953
+ ) {
954
+ fail(`${configurationName} targets must be a list of target names`);
955
+ }
956
+ const targets = kitTargets
957
+ ? ALL_TARGETS.filter((target) => (kitTargets as string[]).includes(target))
958
+ : [...ALL_TARGETS];
959
+
960
+ const jiti = createJiti(import.meta.url);
961
+ const importModule: ModuleImporter = (absolutePath) =>
962
+ jiti.import(absolutePath);
963
+ const shellModuleAbsolute = resolveShellModule(kitRoot, shell);
964
+ const shellPageAbsolute = resolveShellPage(kitRoot, shell, shellPage);
965
+ const charcellShellAbsolute = resolveCharcellShell(
966
+ shell,
967
+ shellModuleAbsolute
968
+ );
969
+ if (charcellShellAbsolute !== undefined && shellPageAbsolute === undefined) {
970
+ warnings.push(
971
+ `preview.shell names a module that configures the charcell shell and no page, so the host page starts empty and light until the shell loads; ` +
972
+ `name the shell's page beside it: shell: { module: '${shell}', page: '${CHARCELL_SHELL}/page' }`
973
+ );
974
+ }
975
+ const kitPackages = kitPackagesOf(kitRoot, kitConfiguration.packages);
976
+ const sharedSpecifiers = sharedSpecifiersOf(
977
+ kitRoot,
978
+ kitConfiguration.add?.shared,
979
+ (kitPackages ?? []).map(({ directoryAbsolute }) => directoryAbsolute)
980
+ );
981
+
982
+ return {
983
+ kitRoot,
984
+ ...(kitPackages === undefined ? {} : { kitPackages }),
985
+ configurationPath,
986
+ configurationModules: [...configurationModules].filter(
987
+ (module) => module !== configurationPath
988
+ ),
989
+ warnings,
990
+ kitName: kitConfiguration.name ?? 'kit',
991
+ kitVersion: readPackageVersion(kitRoot),
992
+ targets,
993
+ buildsHtml: (kitTargets as string[] | undefined)?.includes('html') ?? false,
994
+ svelteMode: kitConfiguration.svelte?.mode === 'runes' ? 'runes' : 'legacy',
995
+ generatedRoot: resolve(kitRoot, '.preview-generated'),
996
+ staticRoot: resolve(kitRoot, '.preview-static'),
997
+ configuration,
998
+ sources,
999
+ viewsModuleAbsolute: configuration.viewsModule
1000
+ ? resolve(kitRoot, configuration.viewsModule)
1001
+ : undefined,
1002
+ codeBlockModuleAbsolute: configuration.codeBlock
1003
+ ? resolve(kitRoot, configuration.codeBlock.module)
1004
+ : undefined,
1005
+ shellModuleAbsolute: shellModuleAbsolute,
1006
+ ...(shellPageAbsolute === undefined ? {} : { shellPageAbsolute }),
1007
+ ...(charcellShellAbsolute === undefined ? {} : { charcellShellAbsolute }),
1008
+ omitPattern,
1009
+ importModule,
1010
+ styling: {
1011
+ extensions: kitConfiguration.styling ?? [],
1012
+ placement: kitConfiguration.stylesheetPlacement ?? 'in-file',
1013
+ ...(kitConfiguration.bem === undefined
1014
+ ? {}
1015
+ : { bem: kitConfiguration.bem }),
1016
+ ...(kitConfiguration.tailwind === undefined
1017
+ ? {}
1018
+ : { tailwind: kitConfiguration.tailwind }),
1019
+ },
1020
+ parts: partsOf(kitConfiguration.parts),
1021
+ sassLoadPathsAbsolute: (Array.isArray(kitConfiguration.loadPaths)
1022
+ ? kitConfiguration.loadPaths
1023
+ : []
1024
+ )
1025
+ .filter((path): path is string => typeof path === 'string')
1026
+ .map((path) => resolve(kitRoot, path)),
1027
+ sharedModulesAbsolute: sharedSpecifiers
1028
+ .filter(({ source }) => /(^|\/)[^./]*$|\.[cm]?[jt]sx?$/.test(source))
1029
+ .map(({ source }) => resolve(kitRoot, source)),
1030
+ sharedSpecifiers,
1031
+ helperModulesAbsolute: helperModulesOf([
1032
+ kitRoot,
1033
+ ...(kitPackages ?? []).map(({ directoryAbsolute }) => directoryAbsolute),
1034
+ ]),
1035
+ };
1036
+ }
1037
+
1038
+ /** The package whose framework-free entries a story's html has in scope. */
1039
+ const HELPERS_PACKAGE = '@scaffold-ui-kit/helpers';
1040
+
1041
+ /** The entries of `@scaffold-ui-kit/helpers` that import no framework. */
1042
+ const FRAMEWORK_FREE_HELPER_ENTRIES = ['./runtime', './zag'];
1043
+
1044
+ /**
1045
+ * Each framework-free helper entry as the first of the directories that has
1046
+ * the package installed gives it: the package found in `node_modules` from
1047
+ * that directory up, and the entry's file from its `exports` (`import`, else
1048
+ * `default`). Nothing else is searched, so a kit without the package gets
1049
+ * none. An entry the package does not export is left out.
1050
+ */
1051
+ function helperModulesOf(directories: readonly string[]): string[] {
1052
+ for (const directory of directories) {
1053
+ const packageDirectory = installedPackageDirectory(
1054
+ directory,
1055
+ HELPERS_PACKAGE
1056
+ );
1057
+ if (packageDirectory === undefined) continue;
1058
+ let exportsField: unknown;
1059
+ try {
1060
+ exportsField = (
1061
+ JSON.parse(
1062
+ readFileSync(resolve(packageDirectory, 'package.json'), 'utf8')
1063
+ ) as { exports?: unknown }
1064
+ ).exports;
1065
+ } catch {
1066
+ continue;
1067
+ }
1068
+ if (!isPlainObject(exportsField)) continue;
1069
+ return FRAMEWORK_FREE_HELPER_ENTRIES.flatMap((entry) => {
1070
+ const target = exportsField[entry];
1071
+ const file =
1072
+ typeof target === 'string'
1073
+ ? target
1074
+ : isPlainObject(target)
1075
+ ? (target.import ?? target.default)
1076
+ : undefined;
1077
+ return typeof file === 'string'
1078
+ ? [realpathSync(resolve(packageDirectory, file))]
1079
+ : [];
1080
+ });
1081
+ }
1082
+ return [];
1083
+ }
1084
+
1085
+ /**
1086
+ * The directory of a package installed for `start`, as Node finds it: in
1087
+ * `node_modules` of `start` or of a directory above it, its links resolved.
1088
+ */
1089
+ function installedPackageDirectory(
1090
+ start: string,
1091
+ name: string
1092
+ ): string | undefined {
1093
+ let directory = resolve(start);
1094
+ for (;;) {
1095
+ const candidate = resolve(directory, 'node_modules', name);
1096
+ if (existsSync(resolve(candidate, 'package.json'))) {
1097
+ return realpathSync(candidate);
1098
+ }
1099
+ const parent = dirname(directory);
1100
+ if (parent === directory) return undefined;
1101
+ directory = parent;
1102
+ }
1103
+ }
1104
+
1105
+ /**
1106
+ * The kit's shared modules with the specifier its built files import each
1107
+ * by: the one the kit's `package.json` exports give the module, else the
1108
+ * `specifier` a configuration still names.
1109
+ */
1110
+ function sharedSpecifiersOf(
1111
+ kitRoot: string,
1112
+ sharedModules: unknown,
1113
+ packageDirectories: readonly string[]
1114
+ ): { source: string; specifier: string }[] {
1115
+ const authored = (Array.isArray(sharedModules) ? sharedModules : []).filter(
1116
+ (sharedModule): sharedModule is { source: string; specifier?: unknown } =>
1117
+ isPlainObject(sharedModule) && typeof sharedModule.source === 'string'
1118
+ );
1119
+ const { sharedModulesOf } = scaffoldUiKit();
1120
+ const published = sharedModulesOf(
1121
+ kitRoot,
1122
+ authored.map(({ source }) => ({ source })),
1123
+ packageDirectories
1124
+ );
1125
+ return authored.flatMap((sharedModule, index) => {
1126
+ const specifier =
1127
+ published[index].import ??
1128
+ (typeof sharedModule.specifier === 'string'
1129
+ ? sharedModule.specifier
1130
+ : undefined);
1131
+ return specifier === undefined
1132
+ ? []
1133
+ : [{ source: sharedModule.source, specifier }];
1134
+ });
1135
+ }
1136
+
1137
+ /**
1138
+ * The kit's packages besides the kit root's, from its configuration's
1139
+ * `packages`: the styles package, then each target's, their
1140
+ * directories resolved against the kit root. `undefined` for a kit without
1141
+ * `packages`; an entry without a name and directory is left out, as the
1142
+ * kit's build reports it.
1143
+ */
1144
+ function kitPackagesOf(
1145
+ kitRoot: string,
1146
+ packages: unknown
1147
+ ): KitLayoutPackage[] | undefined {
1148
+ if (!isPlainObject(packages)) return undefined;
1149
+ const roles: Array<KitLayoutPackage['role']> = ['styles', ...ALL_TARGETS];
1150
+ return roles.flatMap((role) => {
1151
+ const entry = packages[role];
1152
+ if (
1153
+ !isPlainObject(entry) ||
1154
+ typeof entry.name !== 'string' ||
1155
+ entry.name === '' ||
1156
+ typeof entry.directory !== 'string' ||
1157
+ entry.directory === ''
1158
+ )
1159
+ return [];
1160
+ return [
1161
+ {
1162
+ role,
1163
+ name: entry.name,
1164
+ directoryAbsolute: resolve(kitRoot, entry.directory),
1165
+ },
1166
+ ];
1167
+ });
1168
+ }