livemark 0.25.0 → 0.27.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 (427) hide show
  1. package/README.md +3 -2
  2. package/build/actions/config/define.d.ts +12 -0
  3. package/build/actions/config/define.unit.js +8 -1
  4. package/build/actions/escape/copy.d.ts +11 -0
  5. package/build/actions/escape/copy.js +29 -0
  6. package/build/actions/escape/copy.unit.d.ts +1 -0
  7. package/build/actions/escape/copy.unit.js +37 -0
  8. package/build/actions/escape/list.d.ts +12 -0
  9. package/build/actions/escape/list.js +27 -0
  10. package/build/actions/escape/list.unit.d.ts +1 -0
  11. package/build/actions/escape/list.unit.js +36 -0
  12. package/build/actions/target/build.d.ts +9 -26
  13. package/build/actions/target/build.js +50 -71
  14. package/build/actions/target/build.unit.d.ts +1 -0
  15. package/build/actions/target/build.unit.js +76 -0
  16. package/build/actions/target/clear.d.ts +4 -0
  17. package/build/actions/target/clear.js +14 -0
  18. package/build/actions/target/clear.unit.d.ts +1 -0
  19. package/build/actions/target/clear.unit.js +23 -0
  20. package/build/commands/build.js +1 -2
  21. package/build/commands/clear.d.ts +5 -0
  22. package/build/commands/clear.js +15 -0
  23. package/build/commands/escape.d.ts +4 -3
  24. package/build/commands/escape.js +45 -48
  25. package/build/commands/preview.js +1 -2
  26. package/build/commands/start.js +1 -2
  27. package/build/components/Abbr.d.ts +1 -1
  28. package/build/components/Abbr.js +2 -4
  29. package/build/components/AnsiCode.d.ts +1 -1
  30. package/build/components/AnsiCode.js +2 -2
  31. package/build/components/Article.d.ts +2 -1
  32. package/build/components/Article.js +7 -14
  33. package/build/components/BackToTop.d.ts +1 -1
  34. package/build/components/Banner.d.ts +2 -2
  35. package/build/components/Banner.js +7 -3
  36. package/build/components/BlogIndex.d.ts +1 -1
  37. package/build/components/BlogIndex.js +2 -11
  38. package/build/components/Breadcrumbs.d.ts +1 -1
  39. package/build/components/Breadcrumbs.js +2 -3
  40. package/build/components/Callout.d.ts +1 -1
  41. package/build/components/Callout.js +5 -9
  42. package/build/components/Card.d.ts +1 -1
  43. package/build/components/Card.js +2 -3
  44. package/build/components/Cards.d.ts +1 -1
  45. package/build/components/ChangelogIndex.d.ts +1 -1
  46. package/build/components/ChangelogIndex.js +2 -11
  47. package/build/components/ChoiceList.d.ts +15 -0
  48. package/build/components/ChoiceList.js +13 -0
  49. package/build/components/CodeBlock.d.ts +1 -1
  50. package/build/components/CodeBlock.js +3 -16
  51. package/build/components/CodeTabs.d.ts +1 -1
  52. package/build/components/CodeTabs.js +2 -3
  53. package/build/components/Column.d.ts +1 -1
  54. package/build/components/Columns.d.ts +1 -1
  55. package/build/components/Content.d.ts +1 -1
  56. package/build/components/ContentTab.d.ts +1 -1
  57. package/build/components/ContentTabs.d.ts +1 -1
  58. package/build/components/ContentTabs.js +2 -3
  59. package/build/components/DefaultCatchBoundary.d.ts +1 -1
  60. package/build/components/DefaultCatchBoundary.js +5 -7
  61. package/build/components/DefinitionList.d.ts +3 -3
  62. package/build/components/Details.d.ts +1 -1
  63. package/build/components/Details.js +2 -5
  64. package/build/components/FileTree.d.ts +1 -1
  65. package/build/components/FileTree.js +3 -5
  66. package/build/components/Footer.d.ts +1 -1
  67. package/build/components/Footer.js +2 -5
  68. package/build/components/FootnoteRef.d.ts +1 -1
  69. package/build/components/FootnoteRef.js +2 -4
  70. package/build/components/Header.d.ts +6 -0
  71. package/build/components/Header.js +26 -0
  72. package/build/components/HeaderMenu.d.ts +2 -0
  73. package/build/components/HeaderMenu.js +27 -0
  74. package/build/components/HeaderNav.d.ts +6 -0
  75. package/build/components/HeaderNav.js +74 -0
  76. package/build/components/HeaderSearch.d.ts +4 -0
  77. package/build/components/HeaderSearch.js +33 -0
  78. package/build/components/Heading.d.ts +6 -6
  79. package/build/components/InlineBadge.d.ts +2 -2
  80. package/build/components/InlineBadge.js +1 -1
  81. package/build/components/InlineIcon.d.ts +1 -1
  82. package/build/components/InlineToc.d.ts +1 -1
  83. package/build/components/InlineToc.js +2 -4
  84. package/build/components/Layout.d.ts +2 -1
  85. package/build/components/Layout.js +5 -58
  86. package/build/components/LinkButton.d.ts +2 -2
  87. package/build/components/LinkButton.js +1 -1
  88. package/build/components/MenuSheet.d.ts +22 -0
  89. package/build/components/MenuSheet.js +27 -0
  90. package/build/components/Mermaid.d.ts +1 -1
  91. package/build/components/MermaidRenderer.d.ts +1 -1
  92. package/build/components/MermaidRenderer.js +20 -8
  93. package/build/components/NotFound.d.ts +1 -1
  94. package/build/components/NotFound.js +3 -6
  95. package/build/components/PackageTabs.d.ts +1 -1
  96. package/build/components/PackageTabs.js +3 -7
  97. package/build/components/PageToolbar.d.ts +1 -1
  98. package/build/components/PageToolbar.js +2 -10
  99. package/build/components/PrevNext.d.ts +1 -1
  100. package/build/components/PrevNext.js +2 -10
  101. package/build/components/Root.d.ts +4 -0
  102. package/build/components/Root.js +8 -0
  103. package/build/components/Search.d.ts +5 -2
  104. package/build/components/Search.js +11 -9
  105. package/build/components/SearchDialog.d.ts +1 -1
  106. package/build/components/SearchDialog.js +10 -133
  107. package/build/components/Section.d.ts +1 -1
  108. package/build/components/SectionHeader.d.ts +1 -1
  109. package/build/components/SheetPanel.d.ts +12 -0
  110. package/build/components/SheetPanel.js +19 -0
  111. package/build/components/Sidebar.d.ts +1 -1
  112. package/build/components/Sidebar.js +12 -14
  113. package/build/components/SidebarArticles.d.ts +4 -1
  114. package/build/components/SidebarArticles.js +5 -9
  115. package/build/components/SidebarLinks.d.ts +4 -1
  116. package/build/components/SidebarLinks.js +6 -9
  117. package/build/components/SidebarPosts.d.ts +4 -1
  118. package/build/components/SidebarPosts.js +7 -23
  119. package/build/components/SidebarReleases.d.ts +4 -1
  120. package/build/components/SidebarReleases.js +5 -15
  121. package/build/components/SidebarSectionPicker.d.ts +8 -0
  122. package/build/components/SidebarSectionPicker.js +31 -0
  123. package/build/components/SidebarSections.d.ts +1 -1
  124. package/build/components/SidebarSections.js +5 -9
  125. package/build/components/SiteLink.d.ts +1 -1
  126. package/build/components/SiteTitle.d.ts +1 -1
  127. package/build/components/SiteTitle.js +2 -6
  128. package/build/components/SortableTable.d.ts +1 -1
  129. package/build/components/SoundCloud.d.ts +1 -1
  130. package/build/components/SoundCloud.js +2 -2
  131. package/build/components/TagIndex.d.ts +1 -1
  132. package/build/components/TagIndex.js +2 -11
  133. package/build/components/Theme.d.ts +2 -1
  134. package/build/components/Theme.js +35 -15
  135. package/build/components/Toc.d.ts +5 -4
  136. package/build/components/Toc.js +35 -35
  137. package/build/components/YouTube.d.ts +1 -1
  138. package/build/components/YouTube.js +2 -2
  139. package/build/components/ZoomImage.d.ts +1 -1
  140. package/build/content/article.js +4 -11
  141. package/build/content-collections.js +6 -4
  142. package/build/elements/accordion.d.ts +4 -4
  143. package/build/elements/accordion.js +2 -3
  144. package/build/elements/alert-dialog.d.ts +12 -12
  145. package/build/elements/alert-dialog.js +2 -4
  146. package/build/elements/alert.d.ts +4 -4
  147. package/build/elements/alert.js +2 -2
  148. package/build/elements/aspect-ratio.d.ts +1 -1
  149. package/build/elements/avatar.d.ts +6 -6
  150. package/build/elements/badge.d.ts +1 -1
  151. package/build/elements/badge.js +3 -1
  152. package/build/elements/breadcrumb.d.ts +6 -6
  153. package/build/elements/breadcrumb.js +2 -4
  154. package/build/elements/button-group.d.ts +2 -2
  155. package/build/elements/button-group.js +5 -5
  156. package/build/elements/button.d.ts +2 -2
  157. package/build/elements/button.js +9 -7
  158. package/build/elements/calendar.d.ts +2 -2
  159. package/build/elements/calendar.js +2 -2
  160. package/build/elements/card.d.ts +7 -7
  161. package/build/elements/card.js +4 -4
  162. package/build/elements/carousel.d.ts +5 -5
  163. package/build/elements/carousel.js +3 -7
  164. package/build/elements/chart.d.ts +6 -6
  165. package/build/elements/chart.js +5 -11
  166. package/build/elements/checkbox.d.ts +1 -1
  167. package/build/elements/collapsible.d.ts +3 -3
  168. package/build/elements/command.d.ts +9 -9
  169. package/build/elements/command.js +5 -11
  170. package/build/elements/context-menu.d.ts +15 -15
  171. package/build/elements/context-menu.js +6 -9
  172. package/build/elements/dialog.d.ts +10 -10
  173. package/build/elements/dialog.js +2 -6
  174. package/build/elements/drawer.d.ts +14 -12
  175. package/build/elements/drawer.js +14 -15
  176. package/build/elements/dropdown-menu.d.ts +15 -15
  177. package/build/elements/dropdown-menu.js +7 -10
  178. package/build/elements/empty.d.ts +6 -6
  179. package/build/elements/empty.js +2 -2
  180. package/build/elements/field.d.ts +10 -10
  181. package/build/elements/field.js +3 -4
  182. package/build/elements/hover-card.d.ts +3 -3
  183. package/build/elements/hover-card.js +2 -2
  184. package/build/elements/input-group.d.ts +7 -7
  185. package/build/elements/input-group.js +5 -5
  186. package/build/elements/input-otp.d.ts +4 -4
  187. package/build/elements/input.d.ts +1 -1
  188. package/build/elements/input.js +2 -2
  189. package/build/elements/item.d.ts +9 -9
  190. package/build/elements/item.js +2 -2
  191. package/build/elements/kbd.d.ts +2 -2
  192. package/build/elements/label.d.ts +1 -1
  193. package/build/elements/menubar.d.ts +16 -16
  194. package/build/elements/menubar.js +9 -11
  195. package/build/elements/native-select.d.ts +3 -3
  196. package/build/elements/native-select.js +2 -4
  197. package/build/elements/navigation-menu.d.ts +8 -8
  198. package/build/elements/navigation-menu.js +3 -5
  199. package/build/elements/pagination.d.ts +7 -7
  200. package/build/elements/pagination.js +4 -10
  201. package/build/elements/popover.d.ts +6 -6
  202. package/build/elements/popover.js +2 -2
  203. package/build/elements/progress.d.ts +5 -5
  204. package/build/elements/progress.js +2 -3
  205. package/build/elements/radio-group.d.ts +2 -2
  206. package/build/elements/resizable.d.ts +3 -3
  207. package/build/elements/scroll-area.d.ts +2 -2
  208. package/build/elements/scroll-area.js +2 -4
  209. package/build/elements/select.d.ts +9 -9
  210. package/build/elements/select.js +4 -9
  211. package/build/elements/separator.d.ts +1 -1
  212. package/build/elements/sheet.d.ts +8 -8
  213. package/build/elements/sheet.js +2 -6
  214. package/build/elements/sidebar.d.ts +19 -19
  215. package/build/elements/sidebar.js +15 -37
  216. package/build/elements/skeleton.d.ts +1 -1
  217. package/build/elements/slider.d.ts +2 -1
  218. package/build/elements/slider.js +2 -3
  219. package/build/elements/sonner.d.ts +1 -1
  220. package/build/elements/spinner.d.ts +1 -1
  221. package/build/elements/switch.d.ts +1 -1
  222. package/build/elements/table.d.ts +8 -8
  223. package/build/elements/tabs.d.ts +4 -4
  224. package/build/elements/tabs.js +3 -3
  225. package/build/elements/textarea.d.ts +1 -1
  226. package/build/elements/textarea.js +2 -2
  227. package/build/elements/toggle-group.d.ts +3 -2
  228. package/build/elements/toggle-group.js +3 -3
  229. package/build/elements/toggle.d.ts +1 -1
  230. package/build/elements/toggle.js +2 -2
  231. package/build/elements/tooltip.d.ts +4 -4
  232. package/build/elements/tooltip.js +2 -3
  233. package/build/head.d.ts +4 -0
  234. package/build/head.js +6 -0
  235. package/build/helpers/agent-skills.d.ts +22 -0
  236. package/build/helpers/agent-skills.js +46 -0
  237. package/build/helpers/agent-skills.unit.d.ts +1 -0
  238. package/build/helpers/agent-skills.unit.js +49 -0
  239. package/build/helpers/article-search.d.ts +19 -0
  240. package/build/helpers/article-search.js +134 -0
  241. package/build/helpers/article.d.ts +13 -0
  242. package/build/helpers/article.js +17 -1
  243. package/build/helpers/dependency-dir.d.ts +5 -0
  244. package/build/helpers/dependency-dir.js +20 -0
  245. package/build/helpers/dependency-dir.unit.d.ts +1 -0
  246. package/build/helpers/dependency-dir.unit.js +25 -0
  247. package/build/helpers/dynamic-icon-resolver.d.ts +1 -1
  248. package/build/helpers/dynamic-icon.d.ts +1 -1
  249. package/build/helpers/head.d.ts +58 -0
  250. package/build/helpers/head.js +80 -0
  251. package/build/helpers/head.unit.d.ts +1 -0
  252. package/build/helpers/head.unit.js +110 -0
  253. package/build/helpers/llms.d.ts +33 -0
  254. package/build/helpers/llms.js +72 -0
  255. package/build/helpers/llms.unit.d.ts +1 -0
  256. package/build/helpers/llms.unit.js +98 -0
  257. package/build/helpers/nav-items.d.ts +11 -0
  258. package/build/helpers/nav-items.js +65 -0
  259. package/build/helpers/render-inline-markdown.d.ts +1 -0
  260. package/build/helpers/render-inline-markdown.js +34 -0
  261. package/build/helpers/render-inline-markdown.unit.d.ts +1 -0
  262. package/build/helpers/render-inline-markdown.unit.js +20 -0
  263. package/build/helpers/search-highlight.d.ts +8 -0
  264. package/build/helpers/search-highlight.js +59 -0
  265. package/build/helpers/search-highlight.unit.d.ts +1 -0
  266. package/build/helpers/search-highlight.unit.js +36 -0
  267. package/build/helpers/target-export.d.ts +9 -0
  268. package/build/helpers/target-export.js +24 -0
  269. package/build/helpers/target-export.unit.d.ts +1 -0
  270. package/build/helpers/target-export.unit.js +25 -0
  271. package/build/helpers/theme.d.ts +6 -2
  272. package/build/helpers/theme.js +40 -7
  273. package/build/helpers/theme.unit.d.ts +1 -0
  274. package/build/helpers/theme.unit.js +30 -0
  275. package/build/hooks/hydrated.d.ts +2 -0
  276. package/build/hooks/hydrated.js +15 -0
  277. package/build/index.d.ts +1 -0
  278. package/build/index.js +1 -1
  279. package/build/models/config.d.ts +52 -6
  280. package/build/models/config.js +17 -1
  281. package/build/models/section.d.ts +4 -2
  282. package/build/models/section.js +1 -1
  283. package/build/plugins/vite-agents.d.ts +17 -0
  284. package/build/plugins/vite-agents.js +109 -0
  285. package/build/plugins/vite-agents.unit.d.ts +1 -0
  286. package/build/plugins/vite-agents.unit.js +20 -0
  287. package/build/plugins/vite-livemark.d.ts +4 -5
  288. package/build/plugins/vite-livemark.js +56 -90
  289. package/build/plugins/vite-robots.d.ts +4 -2
  290. package/build/plugins/vite-robots.js +15 -2
  291. package/build/plugins/vite-robots.unit.js +5 -1
  292. package/build/program.js +4 -2
  293. package/build/routes/$.d.ts +4 -1
  294. package/build/routes/$.js +20 -9
  295. package/build/routes/__root.js +29 -63
  296. package/build/settings.js +2 -1
  297. package/build/vite.config.js +3 -1
  298. package/package.json +69 -66
  299. package/{.agents/skills → skills}/livemark-authoring/SKILL.md +2 -2
  300. package/{.agents/skills → skills}/livemark-configuration/SKILL.md +16 -5
  301. package/skills/livemark-customization/SKILL.md +146 -0
  302. package/source/actions/config/define.unit.ts +9 -0
  303. package/source/actions/escape/copy.ts +35 -0
  304. package/source/actions/escape/copy.unit.ts +51 -0
  305. package/source/actions/escape/list.ts +30 -0
  306. package/source/actions/escape/list.unit.ts +42 -0
  307. package/source/actions/target/build.ts +49 -69
  308. package/source/actions/target/build.unit.ts +95 -0
  309. package/source/actions/target/clear.ts +14 -0
  310. package/source/actions/target/clear.unit.ts +25 -0
  311. package/source/commands/build.ts +0 -1
  312. package/source/commands/clear.ts +15 -0
  313. package/source/commands/escape.ts +42 -59
  314. package/source/commands/preview.ts +0 -1
  315. package/source/commands/start.ts +0 -1
  316. package/source/components/AnsiCode.tsx +1 -1
  317. package/source/components/Article.tsx +8 -4
  318. package/source/components/Banner.tsx +12 -2
  319. package/source/components/BlogIndex.tsx +1 -1
  320. package/source/components/Callout.tsx +4 -6
  321. package/source/components/ChangelogIndex.tsx +1 -1
  322. package/source/components/ChoiceList.tsx +69 -0
  323. package/source/components/CodeBlock.tsx +6 -6
  324. package/source/components/CodeTabs.tsx +1 -1
  325. package/source/components/ContentTabs.tsx +1 -1
  326. package/source/components/DefaultCatchBoundary.tsx +4 -6
  327. package/source/components/Details.tsx +1 -1
  328. package/source/components/FileTree.tsx +1 -1
  329. package/source/components/Header.tsx +52 -0
  330. package/source/components/HeaderMenu.tsx +41 -0
  331. package/source/components/HeaderNav.tsx +145 -0
  332. package/source/components/HeaderSearch.tsx +77 -0
  333. package/source/components/InlineBadge.tsx +1 -1
  334. package/source/components/InlineToc.tsx +1 -1
  335. package/source/components/Layout.tsx +16 -160
  336. package/source/components/LinkButton.tsx +9 -1
  337. package/source/components/MenuSheet.tsx +92 -0
  338. package/source/components/MermaidRenderer.tsx +19 -8
  339. package/source/components/NotFound.tsx +3 -5
  340. package/source/components/PackageTabs.tsx +2 -2
  341. package/source/components/PageToolbar.tsx +11 -3
  342. package/source/components/PrevNext.tsx +2 -2
  343. package/source/components/Root.tsx +13 -0
  344. package/source/components/Search.tsx +28 -8
  345. package/source/components/SearchDialog.tsx +15 -189
  346. package/source/components/SheetPanel.tsx +69 -0
  347. package/source/components/Sidebar.tsx +27 -14
  348. package/source/components/SidebarArticles.tsx +4 -3
  349. package/source/components/SidebarLinks.tsx +3 -2
  350. package/source/components/SidebarPosts.tsx +3 -2
  351. package/source/components/SidebarReleases.tsx +3 -2
  352. package/source/components/SidebarSectionPicker.tsx +70 -0
  353. package/source/components/SiteTitle.tsx +5 -5
  354. package/source/components/SoundCloud.tsx +1 -1
  355. package/source/components/TagIndex.tsx +1 -1
  356. package/source/components/Theme.tsx +55 -29
  357. package/source/components/Toc.tsx +76 -55
  358. package/source/components/YouTube.tsx +1 -1
  359. package/source/content/article.ts +8 -13
  360. package/source/content-collections.ts +5 -4
  361. package/source/elements/alert-dialog.tsx +1 -1
  362. package/source/elements/alert.tsx +1 -1
  363. package/source/elements/badge.tsx +4 -0
  364. package/source/elements/button-group.tsx +4 -4
  365. package/source/elements/button.tsx +10 -6
  366. package/source/elements/calendar.tsx +1 -1
  367. package/source/elements/card.tsx +3 -3
  368. package/source/elements/command.tsx +7 -4
  369. package/source/elements/context-menu.tsx +5 -5
  370. package/source/elements/dialog.tsx +1 -1
  371. package/source/elements/drawer.tsx +59 -43
  372. package/source/elements/dropdown-menu.tsx +6 -6
  373. package/source/elements/empty.tsx +1 -1
  374. package/source/elements/field.tsx +1 -1
  375. package/source/elements/hover-card.tsx +1 -1
  376. package/source/elements/input-group.tsx +4 -4
  377. package/source/elements/input.tsx +1 -1
  378. package/source/elements/item.tsx +1 -1
  379. package/source/elements/menubar.tsx +8 -8
  380. package/source/elements/native-select.tsx +1 -1
  381. package/source/elements/popover.tsx +1 -1
  382. package/source/elements/select.tsx +3 -3
  383. package/source/elements/sidebar.tsx +8 -21
  384. package/source/elements/tabs.tsx +2 -2
  385. package/source/elements/textarea.tsx +1 -1
  386. package/source/elements/toggle-group.tsx +2 -2
  387. package/source/elements/toggle.tsx +1 -1
  388. package/source/env.d.ts +1 -0
  389. package/source/head.ts +7 -0
  390. package/source/helpers/agent-skills.ts +59 -0
  391. package/source/helpers/agent-skills.unit.ts +56 -0
  392. package/source/helpers/article-search.tsx +192 -0
  393. package/source/helpers/article.ts +17 -0
  394. package/source/helpers/dependency-dir.ts +20 -0
  395. package/source/helpers/dependency-dir.unit.ts +39 -0
  396. package/source/helpers/head.ts +105 -0
  397. package/source/helpers/head.unit.ts +118 -0
  398. package/source/helpers/llms.ts +109 -0
  399. package/source/helpers/llms.unit.ts +115 -0
  400. package/source/helpers/nav-items.tsx +102 -0
  401. package/source/helpers/render-inline-markdown.ts +44 -0
  402. package/source/helpers/render-inline-markdown.unit.ts +30 -0
  403. package/source/helpers/search-highlight.ts +73 -0
  404. package/source/helpers/search-highlight.unit.ts +42 -0
  405. package/source/helpers/target-export.ts +28 -0
  406. package/source/helpers/target-export.unit.ts +37 -0
  407. package/source/helpers/theme.ts +40 -6
  408. package/source/helpers/theme.unit.ts +32 -0
  409. package/source/hooks/hydrated.ts +18 -0
  410. package/source/index.ts +1 -0
  411. package/source/models/config.ts +24 -0
  412. package/source/models/section.ts +3 -0
  413. package/source/plugins/vite-agents.ts +135 -0
  414. package/source/plugins/vite-agents.unit.ts +26 -0
  415. package/source/plugins/vite-livemark.ts +54 -95
  416. package/source/plugins/vite-robots.ts +18 -3
  417. package/source/plugins/vite-robots.unit.ts +7 -0
  418. package/source/program.ts +2 -0
  419. package/source/routes/$.tsx +27 -9
  420. package/source/routes/__root.tsx +51 -71
  421. package/source/settings.ts +1 -0
  422. package/source/styles/general.css +59 -11
  423. package/source/styles/markdown.css +13 -7
  424. package/source/styles/shape.css +14 -0
  425. package/source/vite.config.ts +2 -0
  426. package/.agents/skills/change-review/SKILL.md +0 -47
  427. package/.agents/skills/livemark-customization/SKILL.md +0 -118
@@ -31,4 +31,13 @@ describe("defineConfig", () => {
31
31
  const config = defineConfig({})
32
32
  expect(config.include).toBe("**/*.md")
33
33
  })
34
+
35
+ it("should accept an inline Markdown banner", () => {
36
+ const config = defineConfig({ banner: "Hi · [x](/y/)" })
37
+ expect(config.banner).toBe("Hi · [x](/y/)")
38
+ })
39
+
40
+ it("should leave banner unset by default", () => {
41
+ expect(defineConfig({}).banner).toBeUndefined()
42
+ })
34
43
  })
@@ -0,0 +1,35 @@
1
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"
2
+ import { dirname, join } from "node:path"
3
+ import { ESCAPABLE_FILES, ESCAPABLE_FOLDERS } from "../../settings.ts"
4
+
5
+ /**
6
+ * Copy a livemark default into `<root>/.livemark/<path>`.
7
+ *
8
+ * Throws when the path is not escapable, is not a livemark file, or the
9
+ * destination already exists. Returns the destination path.
10
+ */
11
+ export function copyEscape(props: { root: string; sourceRoot: string; path: string }) {
12
+ const isTopLevelFile = !props.path.includes("/") && ESCAPABLE_FILES.includes(props.path)
13
+ const [first] = props.path.split("/")
14
+ const isFolderPath =
15
+ !!first && ESCAPABLE_FOLDERS.includes(first) && !props.path.includes("..")
16
+ if (!isTopLevelFile && !isFolderPath) {
17
+ throw new Error(
18
+ `'${props.path}' is not escapable (folders: ${ESCAPABLE_FOLDERS.join(", ")}; files: ${ESCAPABLE_FILES.join(", ")})`,
19
+ )
20
+ }
21
+
22
+ const src = join(props.sourceRoot, props.path)
23
+ if (!existsSync(src) || !statSync(src).isFile()) {
24
+ throw new Error(`'${props.path}' is not a livemark file`)
25
+ }
26
+
27
+ const dest = join(props.root, ".livemark", props.path)
28
+ if (existsSync(dest)) {
29
+ throw new Error(`.livemark/${props.path} already exists`)
30
+ }
31
+
32
+ mkdirSync(dirname(dest), { recursive: true })
33
+ writeFileSync(dest, readFileSync(src))
34
+ return dest
35
+ }
@@ -0,0 +1,51 @@
1
+ import fs from "node:fs"
2
+ import { join } from "node:path"
3
+ import { temporaryDirectoryTask } from "tempy"
4
+ import { describe, expect, it } from "vite-plus/test"
5
+ import { copyEscape } from "./copy.ts"
6
+
7
+ describe("copyEscape", () => {
8
+ it("should copy a default into .livemark/", () =>
9
+ temporaryDirectoryTask(async tmpDir => {
10
+ const sourceRoot = createSource(tmpDir)
11
+ const dest = copyEscape({ root: tmpDir, sourceRoot, path: "components/Footer.tsx" })
12
+ expect(dest).toBe(join(tmpDir, ".livemark", "components", "Footer.tsx"))
13
+ expect(fs.readFileSync(dest, "utf8")).toBe("default\n")
14
+ }))
15
+
16
+ it("should refuse to overwrite an existing override", () =>
17
+ temporaryDirectoryTask(async tmpDir => {
18
+ const sourceRoot = createSource(tmpDir)
19
+ copyEscape({ root: tmpDir, sourceRoot, path: "client.tsx" })
20
+ expect(() => copyEscape({ root: tmpDir, sourceRoot, path: "client.tsx" })).toThrow(
21
+ /already exists/,
22
+ )
23
+ }))
24
+
25
+ it("should reject paths outside the escape contract", () =>
26
+ temporaryDirectoryTask(async tmpDir => {
27
+ const sourceRoot = createSource(tmpDir)
28
+ for (const path of ["index.ts", "components/../index.ts", "helpers/toc.ts"]) {
29
+ expect(() => copyEscape({ root: tmpDir, sourceRoot, path })).toThrow(
30
+ /not escapable/,
31
+ )
32
+ }
33
+ }))
34
+
35
+ it("should reject an escapable path with no livemark file", () =>
36
+ temporaryDirectoryTask(async tmpDir => {
37
+ const sourceRoot = createSource(tmpDir)
38
+ expect(() =>
39
+ copyEscape({ root: tmpDir, sourceRoot, path: "components/Missing.tsx" }),
40
+ ).toThrow(/not a livemark file/)
41
+ }))
42
+ })
43
+
44
+ function createSource(tmpDir: string) {
45
+ const sourceRoot = join(tmpDir, "source")
46
+ fs.mkdirSync(join(sourceRoot, "components"), { recursive: true })
47
+ fs.writeFileSync(join(sourceRoot, "components", "Footer.tsx"), "default\n")
48
+ fs.writeFileSync(join(sourceRoot, "client.tsx"), "default\n")
49
+ fs.writeFileSync(join(sourceRoot, "index.ts"), "default\n")
50
+ return sourceRoot
51
+ }
@@ -0,0 +1,30 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import { ESCAPABLE_FILES, ESCAPABLE_FOLDERS } from "../../settings.ts"
4
+
5
+ /** An escapable livemark file and whether the project already shadows it. */
6
+ export interface Escape {
7
+ path: string
8
+ escaped: boolean
9
+ }
10
+
11
+ /**
12
+ * List every file a project can shadow under `.livemark/`, sorted by path.
13
+ */
14
+ export function listEscapes(props: { root: string; sourceRoot: string }) {
15
+ const paths: string[] = []
16
+ for (const folder of ESCAPABLE_FOLDERS) {
17
+ const dir = join(props.sourceRoot, folder)
18
+ if (!existsSync(dir)) continue
19
+ for (const entry of readdirSync(dir)) {
20
+ if (statSync(join(dir, entry)).isFile()) paths.push(`${folder}/${entry}`)
21
+ }
22
+ }
23
+ for (const file of ESCAPABLE_FILES) {
24
+ if (existsSync(join(props.sourceRoot, file))) paths.push(file)
25
+ }
26
+ return paths.sort().map((path): Escape => ({
27
+ path,
28
+ escaped: existsSync(join(props.root, ".livemark", path)),
29
+ }))
30
+ }
@@ -0,0 +1,42 @@
1
+ import fs from "node:fs"
2
+ import { join } from "node:path"
3
+ import { temporaryDirectoryTask } from "tempy"
4
+ import { describe, expect, it } from "vite-plus/test"
5
+ import { listEscapes } from "./list.ts"
6
+
7
+ describe("listEscapes", () => {
8
+ it("should list folder files and top-level files, sorted", () =>
9
+ temporaryDirectoryTask(async tmpDir => {
10
+ const sourceRoot = join(tmpDir, "source")
11
+ writeFile(join(sourceRoot, "components", "Footer.tsx"))
12
+ writeFile(join(sourceRoot, "styles", "general.css"))
13
+ writeFile(join(sourceRoot, "client.tsx"))
14
+ writeFile(join(sourceRoot, "index.ts"))
15
+ fs.mkdirSync(join(sourceRoot, "elements", "nested"), { recursive: true })
16
+
17
+ const escapes = listEscapes({ root: tmpDir, sourceRoot })
18
+ expect(escapes.map(escape => escape.path)).toEqual([
19
+ "client.tsx",
20
+ "components/Footer.tsx",
21
+ "styles/general.css",
22
+ ])
23
+ }))
24
+
25
+ it("should mark files the project already shadows", () =>
26
+ temporaryDirectoryTask(async tmpDir => {
27
+ const sourceRoot = join(tmpDir, "source")
28
+ writeFile(join(sourceRoot, "components", "Footer.tsx"))
29
+ writeFile(join(sourceRoot, "components", "Header.tsx"))
30
+ writeFile(join(tmpDir, ".livemark", "components", "Footer.tsx"))
31
+
32
+ expect(listEscapes({ root: tmpDir, sourceRoot })).toEqual([
33
+ { path: "components/Footer.tsx", escaped: true },
34
+ { path: "components/Header.tsx", escaped: false },
35
+ ])
36
+ }))
37
+ })
38
+
39
+ function writeFile(path: string) {
40
+ fs.mkdirSync(join(path, ".."), { recursive: true })
41
+ fs.writeFileSync(path, "default\n")
42
+ }
@@ -1,86 +1,66 @@
1
- import { createHash } from "node:crypto"
2
- import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs"
3
- import { dirname, join } from "node:path"
1
+ import { cpSync, existsSync, mkdirSync, readlinkSync, rmSync, symlinkSync } from "node:fs"
2
+ import { basename, dirname, join, relative, resolve } from "node:path"
3
+ import { resolveDependencyDir } from "../../helpers/dependency-dir.ts"
4
4
  import { ESCAPABLE_FILES, ESCAPABLE_FOLDERS } from "../../settings.ts"
5
5
 
6
- // This file ships at <livemark-pkg>/source/actions/target/build.ts AND
7
- // gets copied into <livemark-pkg>/targets/<hash>/actions/target/build.ts
8
- // as part of every per-consumer mount. content-collections loads the
9
- // COPIED version, so `import.meta.dirname` can land inside `targets/`.
10
- // Walk up to the first directory holding livemark's `package.json` so
11
- // SOURCE_DIR / TARGETS_ROOT always anchor at the package, never at a
12
- // per-consumer dir.
13
- const LIVEMARK_PKG = findLivemarkPkg(import.meta.dirname)
14
- const SOURCE_DIR = join(LIVEMARK_PKG, "source")
15
- const TARGETS_ROOT = join(LIVEMARK_PKG, "targets")
16
-
17
- function findLivemarkPkg(start: string) {
18
- let cur = start
19
- while (cur !== dirname(cur)) {
20
- if (existsSync(join(cur, "package.json"))) return cur
21
- cur = dirname(cur)
22
- }
23
- throw new Error(`Could not locate livemark package root from ${start}`)
24
- }
25
-
26
6
  /**
27
- * Synthesize a fully self-contained per-consumer Vite mount at
28
- * `<livemark-pkg>/targets/<hash>/`.
29
- *
30
- * The mount is built by copying livemark's `source/` wholesale, then
31
- * overlaying any matching files from the consumer's `.livemark/`. Every
32
- * file lives at its target path as a real file — no symlinks — so
33
- * Vite's relative-import and bare-specifier resolution all anchor
34
- * inside the target dir, where node_modules walks reach livemark's pnpm
35
- * subtree natively.
36
- *
37
- * Behavior:
38
- * - First run (target dir missing): full source copy + overlay.
39
- * - Subsequent runs: skip the source copy. Source is immutable in
40
- * installed mode; in livemark's own dev the watcher keeps the target
41
- * in sync. Only the `.livemark/` overlay is re-applied each time —
42
- * cheap, and may have changed between runs.
43
- * - With `--clear` in argv: wipe the entire target dir (including
44
- * downstream caches like `.vite/`, `.content-collections/`,
45
- * `cache/`, `routeTree.gen.ts`) and rebuild from scratch. Use this
46
- * to recover from a stale or corrupted cache.
47
- *
48
- * Returns the absolute path to the target dir.
7
+ * Synthesize the project's Vite mount at `.livemark/.target/`: livemark's `source/` with
8
+ * the project's `.livemark/` escapes copied over it, plus a `node_modules` link to
9
+ * livemark's dependencies. Escapable folders are rebuilt on every run, so a removed escape
10
+ * never keeps rendering. Returns the target dir.
49
11
  */
50
- export function buildTarget(configPath: string) {
51
- const targetDir = targetDirFor(configPath)
52
- const overridesRoot = join(dirname(configPath), ".livemark")
53
- const clear = process.argv.includes("--clear")
54
-
55
- if (clear) rmSync(targetDir, { recursive: true, force: true })
56
-
57
- if (!existsSync(targetDir)) {
58
- mkdirSync(targetDir, { recursive: true })
59
- cpSync(SOURCE_DIR, targetDir, { recursive: true })
60
- }
12
+ export function buildTarget(props: { configPath: string; sourceDir: string }) {
13
+ const targetDir = targetDirFor(props.configPath)
14
+ const overridesRoot = dirname(targetDir)
15
+ mkdirSync(targetDir, { recursive: true })
61
16
 
17
+ cpSync(props.sourceDir, targetDir, {
18
+ recursive: true,
19
+ filter: src => isPackageFile(src) && !isEscapableFolder(src, props.sourceDir),
20
+ })
62
21
  for (const folder of ESCAPABLE_FOLDERS) {
63
- const overlay = join(overridesRoot, folder)
64
- if (!existsSync(overlay)) continue
65
22
  const dest = join(targetDir, folder)
66
- if (!existsSync(dest)) mkdirSync(dest, { recursive: true })
67
- for (const f of readdirSync(overlay)) {
68
- cpSync(join(overlay, f), join(dest, f), { recursive: true, force: true })
69
- }
23
+ rmSync(dest, { recursive: true, force: true })
24
+ const source = join(props.sourceDir, folder)
25
+ if (existsSync(source))
26
+ cpSync(source, dest, { recursive: true, filter: isPackageFile })
27
+ const overlay = join(overridesRoot, folder)
28
+ if (existsSync(overlay)) cpSync(overlay, dest, { recursive: true })
70
29
  }
71
30
  for (const file of ESCAPABLE_FILES) {
72
31
  const overlay = join(overridesRoot, file)
73
- if (!existsSync(overlay)) continue
74
- cpSync(overlay, join(targetDir, file), { force: true })
32
+ if (existsSync(overlay)) cpSync(overlay, join(targetDir, file))
75
33
  }
76
34
 
35
+ linkDependencies(targetDir, resolveDependencyDir(props.sourceDir))
77
36
  return targetDir
78
37
  }
79
38
 
80
- /** Compute the target dir for a given consumer config path without
81
- * rebuilding it. Used by code that needs to write into the target
82
- * (e.g. cache writers) outside of `buildTarget`'s setup phase. */
39
+ /** Locate the project's target dir without building it. */
83
40
  export function targetDirFor(configPath: string) {
84
- const hash = createHash("sha256").update(configPath).digest("hex").slice(0, 16)
85
- return join(TARGETS_ROOT, hash)
41
+ return join(dirname(configPath), ".livemark", ".target")
42
+ }
43
+
44
+ function isPackageFile(src: string) {
45
+ return !/\.unit\.tsx?$/.test(src) && basename(src) !== "fixtures"
46
+ }
47
+
48
+ function isEscapableFolder(src: string, sourceDir: string) {
49
+ return ESCAPABLE_FOLDERS.includes(relative(sourceDir, src))
50
+ }
51
+
52
+ function linkDependencies(targetDir: string, dependencyDir: string) {
53
+ const link = join(targetDir, "node_modules")
54
+ if (readLink(link) === resolve(dependencyDir)) return
55
+ // Not recursive: a stale link must go without touching the store it points into.
56
+ rmSync(link, { force: true })
57
+ symlinkSync(dependencyDir, link, "junction")
58
+ }
59
+
60
+ function readLink(path: string) {
61
+ try {
62
+ return resolve(dirname(path), readlinkSync(path))
63
+ } catch {
64
+ return undefined
65
+ }
86
66
  }
@@ -0,0 +1,95 @@
1
+ import { existsSync, readFileSync, readlinkSync, realpathSync } from "node:fs"
2
+ import { mkdir, rm, symlink, writeFile } from "node:fs/promises"
3
+ import { dirname, join } from "node:path"
4
+ import { temporaryDirectoryTask } from "tempy"
5
+ import { describe, expect, it } from "vite-plus/test"
6
+ import { buildTarget, targetDirFor } from "./build.ts"
7
+
8
+ describe("buildTarget", () => {
9
+ it("copies the source, overlays escapes and skips unit tests", () =>
10
+ temporaryDirectoryTask(async root => {
11
+ const { configPath, sourceDir } = await createFixture(root)
12
+ await put(join(root, "project/.livemark/components/Footer.tsx"), "escaped")
13
+ await put(join(root, "project/.livemark/head.ts"), "escaped head")
14
+
15
+ const targetDir = buildTarget({ configPath, sourceDir })
16
+
17
+ expect(targetDir).toBe(join(root, "project/.livemark/.target"))
18
+ expect(read(targetDir, "components/Footer.tsx")).toBe("escaped")
19
+ expect(read(targetDir, "components/Header.tsx")).toBe("default")
20
+ expect(read(targetDir, "head.ts")).toBe("escaped head")
21
+ expect(read(targetDir, "helpers/util.ts")).toBe("util")
22
+ expect(existsSync(join(targetDir, "helpers/util.unit.ts"))).toBe(false)
23
+ }))
24
+
25
+ it("drops escapes removed since the last run", () =>
26
+ temporaryDirectoryTask(async root => {
27
+ const { configPath, sourceDir } = await createFixture(root)
28
+ const overrides = join(root, "project/.livemark")
29
+ await put(join(overrides, "components/Footer.tsx"), "escaped")
30
+ await put(join(overrides, "routes/about.tsx"), "about")
31
+ await put(join(overrides, "head.ts"), "escaped head")
32
+ buildTarget({ configPath, sourceDir })
33
+
34
+ await rm(join(overrides, "components"), { recursive: true })
35
+ await rm(join(overrides, "routes"), { recursive: true })
36
+ await rm(join(overrides, "head.ts"))
37
+ const targetDir = buildTarget({ configPath, sourceDir })
38
+
39
+ expect(read(targetDir, "components/Footer.tsx")).toBe("default")
40
+ expect(existsSync(join(targetDir, "routes/about.tsx"))).toBe(false)
41
+ expect(read(targetDir, "head.ts")).toBe("head")
42
+ }))
43
+
44
+ it("keeps generated entries between runs", () =>
45
+ temporaryDirectoryTask(async root => {
46
+ const { configPath, sourceDir } = await createFixture(root)
47
+ const targetDir = buildTarget({ configPath, sourceDir })
48
+ await put(join(targetDir, "cache/changelog/entry.md"), "cached")
49
+
50
+ buildTarget({ configPath, sourceDir })
51
+
52
+ expect(read(targetDir, "cache/changelog/entry.md")).toBe("cached")
53
+ }))
54
+
55
+ it("links node_modules to livemark's dependencies and repairs a stale link", () =>
56
+ temporaryDirectoryTask(async root => {
57
+ const { configPath, sourceDir } = await createFixture(root)
58
+ const link = join(targetDirFor(configPath), "node_modules")
59
+ const dependencyDir = realpathSync(join(root, "livemark/node_modules"))
60
+
61
+ buildTarget({ configPath, sourceDir })
62
+ expect(readlinkSync(link)).toBe(dependencyDir)
63
+
64
+ await rm(link)
65
+ await mkdir(join(root, "elsewhere"))
66
+ await symlink(join(root, "elsewhere"), link, "junction")
67
+ buildTarget({ configPath, sourceDir })
68
+
69
+ expect(readlinkSync(link)).toBe(dependencyDir)
70
+ expect(existsSync(join(root, "elsewhere"))).toBe(true)
71
+ }))
72
+ })
73
+
74
+ async function createFixture(root: string) {
75
+ const sourceDir = join(root, "livemark/source")
76
+ await put(join(sourceDir, "components/Footer.tsx"), "default")
77
+ await put(join(sourceDir, "components/Header.tsx"), "default")
78
+ await put(join(sourceDir, "routes/index.tsx"), "index")
79
+ await put(join(sourceDir, "helpers/util.ts"), "util")
80
+ await put(join(sourceDir, "helpers/util.unit.ts"), "test")
81
+ await put(join(sourceDir, "head.ts"), "head")
82
+ await mkdir(join(root, "livemark/node_modules"))
83
+ const configPath = join(root, "project/livemark.config.ts")
84
+ await put(configPath, "export default {}")
85
+ return { configPath, sourceDir }
86
+ }
87
+
88
+ async function put(path: string, content: string) {
89
+ await mkdir(dirname(path), { recursive: true })
90
+ await writeFile(path, content)
91
+ }
92
+
93
+ function read(dir: string, path: string) {
94
+ return readFileSync(join(dir, path), "utf-8")
95
+ }
@@ -0,0 +1,14 @@
1
+ import { rmSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import { targetDirFor } from "./build.ts"
4
+
5
+ /**
6
+ * Wipe the project's target dir so the next run rebuilds it from scratch.
7
+ */
8
+ export function clearTarget(configPath: string) {
9
+ const targetDir = targetDirFor(configPath)
10
+ // Unlink the dependency link first so the recursive remove cannot reach the store.
11
+ rmSync(join(targetDir, "node_modules"), { force: true })
12
+ rmSync(targetDir, { recursive: true, force: true })
13
+ return targetDir
14
+ }
@@ -0,0 +1,25 @@
1
+ import { existsSync } from "node:fs"
2
+ import { mkdir, symlink, writeFile } from "node:fs/promises"
3
+ import { join } from "node:path"
4
+ import { temporaryDirectoryTask } from "tempy"
5
+ import { describe, expect, it } from "vite-plus/test"
6
+ import { targetDirFor } from "./build.ts"
7
+ import { clearTarget } from "./clear.ts"
8
+
9
+ describe("clearTarget", () => {
10
+ it("removes the target dir without following its dependency link", () =>
11
+ temporaryDirectoryTask(async root => {
12
+ const configPath = join(root, "livemark.config.ts")
13
+ const targetDir = targetDirFor(configPath)
14
+ await mkdir(join(targetDir, "cache"), { recursive: true })
15
+ await writeFile(join(targetDir, "cache", "entry.md"), "cached")
16
+ await mkdir(join(root, "deps"))
17
+ await writeFile(join(root, "deps", "kept.js"), "")
18
+ await symlink(join(root, "deps"), join(targetDir, "node_modules"), "junction")
19
+
20
+ expect(clearTarget(configPath)).toBe(targetDir)
21
+ expect(existsSync(targetDir)).toBe(false)
22
+ expect(existsSync(join(root, "deps", "kept.js"))).toBe(true)
23
+ expect(() => clearTarget(configPath)).not.toThrow()
24
+ }))
25
+ })
@@ -9,7 +9,6 @@ const configFile = join(import.meta.dirname, "..", "..", "source", "vite.config.
9
9
  */
10
10
  export const build = new Command("build")
11
11
  .description("Build the site for production")
12
- .option("--clear", "Wipe and fully rebuild the runtime target dir")
13
12
  .action(async () => {
14
13
  const builder = await createBuilder({ configFile })
15
14
  await builder.buildApp()
@@ -0,0 +1,15 @@
1
+ import { Command } from "commander"
2
+ import pc from "picocolors"
3
+ import { loadConfig } from "../actions/config/load.ts"
4
+ import { clearTarget } from "../actions/target/clear.ts"
5
+
6
+ /**
7
+ * Wipe the generated `.livemark/.target/` mount.
8
+ */
9
+ export const clear = new Command("clear")
10
+ .description("Wipe the generated .livemark/.target/ mount")
11
+ .action(async () => {
12
+ const config = await loadConfig()
13
+ const targetDir = clearTarget(config.configPath)
14
+ console.log(`Cleared ${pc.dim(targetDir)}`)
15
+ })
@@ -1,79 +1,62 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- readdirSync,
5
- readFileSync,
6
- statSync,
7
- writeFileSync,
8
- } from "node:fs"
9
- import { dirname, join } from "node:path"
1
+ import { join } from "node:path"
2
+ import { autocompleteMultiselect, cancel, isCancel } from "@clack/prompts"
10
3
  import { Command } from "commander"
11
4
  import pc from "picocolors"
12
- import { ESCAPABLE_FILES, ESCAPABLE_FOLDERS } from "../settings.ts"
5
+ import { copyEscape } from "../actions/escape/copy.ts"
6
+ import { listEscapes } from "../actions/escape/list.ts"
13
7
 
14
- const defaultsRoot = join(import.meta.dirname, "..", "..", "source")
8
+ const sourceRoot = join(import.meta.dirname, "..", "..", "source")
15
9
 
16
10
  /**
17
- * List or copy escapable livemark files into `.livemark/`.
11
+ * Pick, list or copy escapable livemark files into `.livemark/`.
18
12
  *
19
- * Without an argument, prints every file you can shadow. With a path, copies
20
- * the default from the livemark package into `.livemark/<path>`; fails if the
13
+ * Without an argument, opens a searchable picker in a terminal and prints every
14
+ * file you can shadow otherwise (or with `--list`). With paths, copies each
15
+ * default from the livemark package into `.livemark/<path>`; fails if a
21
16
  * destination already exists.
22
17
  */
23
18
  export const escape = new Command("escape")
24
- .description("List or copy escapable livemark files into .livemark/")
19
+ .description("Pick, list or copy escapable livemark files into .livemark/")
25
20
  .argument(
26
- "[path]",
27
- "path relative to .livemark/ (e.g. components/Footer.tsx, client.tsx)",
21
+ "[paths...]",
22
+ "paths relative to .livemark/ (e.g. components/Footer.tsx, client.tsx)",
28
23
  )
29
- .action((relPath?: string) => {
30
- if (!relPath) {
31
- for (const file of listEscapable()) console.log(file)
24
+ .option("-l, --list", "print every escapable file instead of opening the picker")
25
+ .action(async (paths: string[], options: { list?: boolean }) => {
26
+ const root = process.cwd()
27
+ if (paths.length) return copyPaths(root, paths)
28
+
29
+ const escapes = listEscapes({ root, sourceRoot })
30
+ if (options.list || !process.stdin.isTTY || !process.stdout.isTTY) {
31
+ for (const escape of escapes) console.log(escape.path)
32
32
  console.log(pc.dim("\nRun 'livemark escape <path>' to copy a file into .livemark/"))
33
33
  return
34
34
  }
35
35
 
36
- const isTopLevelFile = !relPath.includes("/") && ESCAPABLE_FILES.includes(relPath)
37
- const [first] = relPath.split("/")
38
- const isFolderPath =
39
- !!first && ESCAPABLE_FOLDERS.includes(first) && !relPath.includes("..")
40
- if (!isTopLevelFile && !isFolderPath) {
41
- console.error(
42
- pc.red(
43
- `Error: '${relPath}' is not escapable (folders: ${ESCAPABLE_FOLDERS.join(", ")}; files: ${ESCAPABLE_FILES.join(", ")})`,
44
- ),
45
- )
46
- process.exit(1)
47
- }
48
-
49
- const src = join(defaultsRoot, relPath)
50
- if (!existsSync(src) || !statSync(src).isFile()) {
51
- console.error(pc.red(`Error: '${relPath}' is not a livemark file`))
52
- process.exit(1)
53
- }
54
-
55
- const dest = join(process.cwd(), ".livemark", relPath)
56
- if (existsSync(dest)) {
57
- console.error(pc.red(`Error: .livemark/${relPath} already exists`))
58
- process.exit(1)
59
- }
60
-
61
- mkdirSync(dirname(dest), { recursive: true })
62
- writeFileSync(dest, readFileSync(src))
63
- console.log(`Copied ${pc.dim(relPath)} → ${pc.green(`.livemark/${relPath}`)}`)
36
+ const selected = await autocompleteMultiselect({
37
+ message: "Select files to escape into .livemark/",
38
+ placeholder: "Type to search...",
39
+ options: escapes.map(escape => ({
40
+ value: escape.path,
41
+ label: escape.path,
42
+ hint: escape.escaped ? "already escaped" : undefined,
43
+ disabled: escape.escaped,
44
+ })),
45
+ required: false,
46
+ })
47
+ if (isCancel(selected)) return cancel("Nothing copied")
48
+ copyPaths(root, selected)
64
49
  })
65
50
 
66
- function listEscapable() {
67
- const files: string[] = []
68
- for (const folder of ESCAPABLE_FOLDERS) {
69
- const dir = join(defaultsRoot, folder)
70
- if (!existsSync(dir)) continue
71
- for (const entry of readdirSync(dir)) {
72
- if (statSync(join(dir, entry)).isFile()) files.push(`${folder}/${entry}`)
51
+ function copyPaths(root: string, paths: string[]) {
52
+ for (const path of paths) {
53
+ try {
54
+ copyEscape({ root, sourceRoot, path })
55
+ console.log(`Copied ${pc.dim(path)} → ${pc.green(`.livemark/${path}`)}`)
56
+ } catch (error) {
57
+ const message = error instanceof Error ? error.message : String(error)
58
+ console.error(pc.red(`Error: ${message}`))
59
+ process.exitCode = 1
73
60
  }
74
61
  }
75
- for (const file of ESCAPABLE_FILES) {
76
- if (existsSync(join(defaultsRoot, file))) files.push(file)
77
- }
78
- return files.sort()
79
62
  }
@@ -9,7 +9,6 @@ const configFile = join(import.meta.dirname, "..", "..", "source", "vite.config.
9
9
  */
10
10
  export const preview = new Command("preview")
11
11
  .description("Preview the production build locally")
12
- .option("--clear", "Wipe and fully rebuild the runtime target dir")
13
12
  .action(async () => {
14
13
  const server = await vitePreview({
15
14
  configFile,
@@ -9,7 +9,6 @@ const configFile = join(import.meta.dirname, "..", "..", "source", "vite.config.
9
9
  */
10
10
  export const start = new Command("start")
11
11
  .description("Start a live server")
12
- .option("--clear", "Wipe and fully rebuild the runtime target dir")
13
12
  .action(async () => {
14
13
  const server = await createServer({
15
14
  configFile,
@@ -1,7 +1,7 @@
1
1
  /** Renders pre-formatted HTML produced from ANSI escape sequences */
2
2
  export function AnsiCode(props: { html: string }) {
3
3
  return (
4
- <figure className="shiki not-prose my-4 relative rounded-xl border shadow-sm overflow-hidden text-sm bg-sidebar dark:bg-sidebar">
4
+ <figure className="shiki not-prose my-4 relative rounded-card border shadow-sm overflow-hidden text-sm bg-sidebar dark:bg-sidebar">
5
5
  <div className="text-[0.8125rem] py-3.5 overflow-auto">
6
6
  <pre className="min-w-full w-max">
7
7
  <code