markopress 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/bin/cli.js +16 -0
  2. package/dist/build/index.d.ts +83 -0
  3. package/dist/build/index.d.ts.map +1 -0
  4. package/dist/build/index.js +1553 -0
  5. package/dist/build/index.js.map +1 -0
  6. package/dist/build/manifest-generator.d.ts +34 -0
  7. package/dist/build/manifest-generator.d.ts.map +1 -0
  8. package/dist/build/manifest-generator.js +86 -0
  9. package/dist/build/manifest-generator.js.map +1 -0
  10. package/dist/build/security.test.d.ts +6 -0
  11. package/dist/build/security.test.d.ts.map +1 -0
  12. package/dist/build/security.test.js +88 -0
  13. package/dist/build/security.test.js.map +1 -0
  14. package/dist/build/types.d.ts +21 -0
  15. package/dist/build/types.d.ts.map +1 -0
  16. package/dist/build/types.js +5 -0
  17. package/dist/build/types.js.map +1 -0
  18. package/dist/build/vite-config.test.d.ts +2 -0
  19. package/dist/build/vite-config.test.d.ts.map +1 -0
  20. package/dist/build/vite-config.test.js +53 -0
  21. package/dist/build/vite-config.test.js.map +1 -0
  22. package/dist/build/vite-markdown-plugin.d.ts +13 -0
  23. package/dist/build/vite-markdown-plugin.d.ts.map +1 -0
  24. package/dist/build/vite-markdown-plugin.js +134 -0
  25. package/dist/build/vite-markdown-plugin.js.map +1 -0
  26. package/dist/build/vite-markdown-plugin.test.d.ts +2 -0
  27. package/dist/build/vite-markdown-plugin.test.d.ts.map +1 -0
  28. package/dist/build/vite-markdown-plugin.test.js +41 -0
  29. package/dist/build/vite-markdown-plugin.test.js.map +1 -0
  30. package/dist/cli/index.d.ts +6 -0
  31. package/dist/cli/index.d.ts.map +1 -0
  32. package/dist/cli/index.js +75 -0
  33. package/dist/cli/index.js.map +1 -0
  34. package/dist/config/app-root.d.ts +11 -0
  35. package/dist/config/app-root.d.ts.map +1 -0
  36. package/dist/config/app-root.js +24 -0
  37. package/dist/config/app-root.js.map +1 -0
  38. package/dist/config/app-root.test.d.ts +2 -0
  39. package/dist/config/app-root.test.d.ts.map +1 -0
  40. package/dist/config/app-root.test.js +71 -0
  41. package/dist/config/app-root.test.js.map +1 -0
  42. package/dist/config/index.d.ts +6 -0
  43. package/dist/config/index.d.ts.map +1 -0
  44. package/dist/config/index.js +6 -0
  45. package/dist/config/index.js.map +1 -0
  46. package/dist/config/loader.d.ts +25 -0
  47. package/dist/config/loader.d.ts.map +1 -0
  48. package/dist/config/loader.js +187 -0
  49. package/dist/config/loader.js.map +1 -0
  50. package/dist/config/loader.test.d.ts +2 -0
  51. package/dist/config/loader.test.d.ts.map +1 -0
  52. package/dist/config/loader.test.js +24 -0
  53. package/dist/config/loader.test.js.map +1 -0
  54. package/dist/config/types.d.ts +149 -0
  55. package/dist/config/types.d.ts.map +1 -0
  56. package/dist/config/types.js +5 -0
  57. package/dist/config/types.js.map +1 -0
  58. package/dist/config/validation.d.ts +188 -0
  59. package/dist/config/validation.d.ts.map +1 -0
  60. package/dist/config/validation.js +139 -0
  61. package/dist/config/validation.js.map +1 -0
  62. package/dist/content/index.d.ts +6 -0
  63. package/dist/content/index.d.ts.map +1 -0
  64. package/dist/content/index.js +6 -0
  65. package/dist/content/index.js.map +1 -0
  66. package/dist/content/registry.d.ts +29 -0
  67. package/dist/content/registry.d.ts.map +1 -0
  68. package/dist/content/registry.js +45 -0
  69. package/dist/content/registry.js.map +1 -0
  70. package/dist/content/scanner.d.ts +9 -0
  71. package/dist/content/scanner.d.ts.map +1 -0
  72. package/dist/content/scanner.js +115 -0
  73. package/dist/content/scanner.js.map +1 -0
  74. package/dist/content/types.d.ts +41 -0
  75. package/dist/content/types.d.ts.map +1 -0
  76. package/dist/content/types.js +5 -0
  77. package/dist/content/types.js.map +1 -0
  78. package/dist/dev/index.d.ts +18 -0
  79. package/dist/dev/index.d.ts.map +1 -0
  80. package/dist/dev/index.js +93 -0
  81. package/dist/dev/index.js.map +1 -0
  82. package/dist/index.d.ts +14 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +17 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/markdown/code.d.ts +79 -0
  87. package/dist/markdown/code.d.ts.map +1 -0
  88. package/dist/markdown/code.js +305 -0
  89. package/dist/markdown/code.js.map +1 -0
  90. package/dist/markdown/containers.d.ts +17 -0
  91. package/dist/markdown/containers.d.ts.map +1 -0
  92. package/dist/markdown/containers.js +143 -0
  93. package/dist/markdown/containers.js.map +1 -0
  94. package/dist/markdown/includes.d.ts +18 -0
  95. package/dist/markdown/includes.d.ts.map +1 -0
  96. package/dist/markdown/includes.js +9 -0
  97. package/dist/markdown/includes.js.map +1 -0
  98. package/dist/markdown/index.d.ts +8 -0
  99. package/dist/markdown/index.d.ts.map +1 -0
  100. package/dist/markdown/index.js +8 -0
  101. package/dist/markdown/index.js.map +1 -0
  102. package/dist/markdown/loader.d.ts +31 -0
  103. package/dist/markdown/loader.d.ts.map +1 -0
  104. package/dist/markdown/loader.js +325 -0
  105. package/dist/markdown/loader.js.map +1 -0
  106. package/dist/markdown/preserve-tags.d.ts +16 -0
  107. package/dist/markdown/preserve-tags.d.ts.map +1 -0
  108. package/dist/markdown/preserve-tags.js +233 -0
  109. package/dist/markdown/preserve-tags.js.map +1 -0
  110. package/dist/markdown/renderer.d.ts +28 -0
  111. package/dist/markdown/renderer.d.ts.map +1 -0
  112. package/dist/markdown/renderer.js +146 -0
  113. package/dist/markdown/renderer.js.map +1 -0
  114. package/dist/markdown/tag-validator.d.ts +64 -0
  115. package/dist/markdown/tag-validator.d.ts.map +1 -0
  116. package/dist/markdown/tag-validator.js +118 -0
  117. package/dist/markdown/tag-validator.js.map +1 -0
  118. package/dist/markdown/types.d.ts +44 -0
  119. package/dist/markdown/types.d.ts.map +1 -0
  120. package/dist/markdown/types.js +5 -0
  121. package/dist/markdown/types.js.map +1 -0
  122. package/dist/plugin/compat.d.ts +14 -0
  123. package/dist/plugin/compat.d.ts.map +1 -0
  124. package/dist/plugin/compat.js +78 -0
  125. package/dist/plugin/compat.js.map +1 -0
  126. package/dist/plugin/context.d.ts +38 -0
  127. package/dist/plugin/context.d.ts.map +1 -0
  128. package/dist/plugin/context.js +103 -0
  129. package/dist/plugin/context.js.map +1 -0
  130. package/dist/plugin/index.d.ts +6 -0
  131. package/dist/plugin/index.d.ts.map +1 -0
  132. package/dist/plugin/index.js +6 -0
  133. package/dist/plugin/index.js.map +1 -0
  134. package/dist/plugin/manager.d.ts +112 -0
  135. package/dist/plugin/manager.d.ts.map +1 -0
  136. package/dist/plugin/manager.js +385 -0
  137. package/dist/plugin/manager.js.map +1 -0
  138. package/dist/plugin/types.d.ts +182 -0
  139. package/dist/plugin/types.d.ts.map +1 -0
  140. package/dist/plugin/types.js +5 -0
  141. package/dist/plugin/types.js.map +1 -0
  142. package/dist/plugins/blog-index/index.d.ts +18 -0
  143. package/dist/plugins/blog-index/index.d.ts.map +1 -0
  144. package/dist/plugins/blog-index/index.js +158 -0
  145. package/dist/plugins/blog-index/index.js.map +1 -0
  146. package/dist/plugins/sidenav/index.d.ts +36 -0
  147. package/dist/plugins/sidenav/index.d.ts.map +1 -0
  148. package/dist/plugins/sidenav/index.js +86 -0
  149. package/dist/plugins/sidenav/index.js.map +1 -0
  150. package/dist/plugins/toc/index.d.ts +38 -0
  151. package/dist/plugins/toc/index.d.ts.map +1 -0
  152. package/dist/plugins/toc/index.js +79 -0
  153. package/dist/plugins/toc/index.js.map +1 -0
  154. package/dist/preview/index.d.ts +7 -0
  155. package/dist/preview/index.d.ts.map +1 -0
  156. package/dist/preview/index.js +25 -0
  157. package/dist/preview/index.js.map +1 -0
  158. package/dist/theme/default/build/generate-all.d.ts +9 -0
  159. package/dist/theme/default/build/generate-all.d.ts.map +1 -0
  160. package/dist/theme/default/build/generate-all.js +85 -0
  161. package/dist/theme/default/build/generate-all.js.map +1 -0
  162. package/dist/theme/default/build/generate-css.d.ts +19 -0
  163. package/dist/theme/default/build/generate-css.d.ts.map +1 -0
  164. package/dist/theme/default/build/generate-css.js +199 -0
  165. package/dist/theme/default/build/generate-css.js.map +1 -0
  166. package/dist/theme/default/build/index.d.ts +5 -0
  167. package/dist/theme/default/build/index.d.ts.map +1 -0
  168. package/dist/theme/default/build/index.js +5 -0
  169. package/dist/theme/default/build/index.js.map +1 -0
  170. package/dist/theme/default/design-systems/default.d.ts +12 -0
  171. package/dist/theme/default/design-systems/default.d.ts.map +1 -0
  172. package/dist/theme/default/design-systems/default.js +289 -0
  173. package/dist/theme/default/design-systems/default.js.map +1 -0
  174. package/dist/theme/default/design-systems/docusaurus.d.ts +12 -0
  175. package/dist/theme/default/design-systems/docusaurus.d.ts.map +1 -0
  176. package/dist/theme/default/design-systems/docusaurus.js +299 -0
  177. package/dist/theme/default/design-systems/docusaurus.js.map +1 -0
  178. package/dist/theme/default/design-systems/index.d.ts +50 -0
  179. package/dist/theme/default/design-systems/index.d.ts.map +1 -0
  180. package/dist/theme/default/design-systems/index.js +54 -0
  181. package/dist/theme/default/design-systems/index.js.map +1 -0
  182. package/dist/theme/default/design-systems/rspress.d.ts +12 -0
  183. package/dist/theme/default/design-systems/rspress.d.ts.map +1 -0
  184. package/dist/theme/default/design-systems/rspress.js +299 -0
  185. package/dist/theme/default/design-systems/rspress.js.map +1 -0
  186. package/dist/theme/default/design-systems/types.d.ts +238 -0
  187. package/dist/theme/default/design-systems/types.d.ts.map +1 -0
  188. package/dist/theme/default/design-systems/types.js +6 -0
  189. package/dist/theme/default/design-systems/types.js.map +1 -0
  190. package/dist/theme/default/design-systems/vitepress.d.ts +12 -0
  191. package/dist/theme/default/design-systems/vitepress.d.ts.map +1 -0
  192. package/dist/theme/default/design-systems/vitepress.js +299 -0
  193. package/dist/theme/default/design-systems/vitepress.js.map +1 -0
  194. package/dist/theme/default/index.d.ts +60 -0
  195. package/dist/theme/default/index.d.ts.map +1 -0
  196. package/dist/theme/default/index.js +44 -0
  197. package/dist/theme/default/index.js.map +1 -0
  198. package/dist/theme/default/theme.d.ts +14 -0
  199. package/dist/theme/default/theme.d.ts.map +1 -0
  200. package/dist/theme/default/theme.js +58 -0
  201. package/dist/theme/default/theme.js.map +1 -0
  202. package/dist/theme/index.d.ts +6 -0
  203. package/dist/theme/index.d.ts.map +1 -0
  204. package/dist/theme/index.js +6 -0
  205. package/dist/theme/index.js.map +1 -0
  206. package/dist/theme/loader.d.ts +21 -0
  207. package/dist/theme/loader.d.ts.map +1 -0
  208. package/dist/theme/loader.js +125 -0
  209. package/dist/theme/loader.js.map +1 -0
  210. package/dist/theme/types.d.ts +73 -0
  211. package/dist/theme/types.d.ts.map +1 -0
  212. package/dist/theme/types.js +5 -0
  213. package/dist/theme/types.js.map +1 -0
  214. package/dist/vite/index.d.ts +6 -0
  215. package/dist/vite/index.d.ts.map +1 -0
  216. package/dist/vite/index.js +6 -0
  217. package/dist/vite/index.js.map +1 -0
  218. package/dist/vite/markdownPlugin.d.ts +15 -0
  219. package/dist/vite/markdownPlugin.d.ts.map +1 -0
  220. package/dist/vite/markdownPlugin.js +111 -0
  221. package/dist/vite/markdownPlugin.js.map +1 -0
  222. package/dist/vite/plugin.d.ts +18 -0
  223. package/dist/vite/plugin.d.ts.map +1 -0
  224. package/dist/vite/plugin.js +94 -0
  225. package/dist/vite/plugin.js.map +1 -0
  226. package/marko.json +3 -0
  227. package/package.json +109 -0
  228. package/src/theme/default/build/generate-all.ts +99 -0
  229. package/src/theme/default/build/generate-css.ts +234 -0
  230. package/src/theme/default/build/index.ts +5 -0
  231. package/src/theme/default/components/doc-footer.marko +180 -0
  232. package/src/theme/default/components/footer.marko +32 -0
  233. package/src/theme/default/components/header.marko +49 -0
  234. package/src/theme/default/components/nav-bar.marko +191 -0
  235. package/src/theme/default/components/page-header.marko +20 -0
  236. package/src/theme/default/components/reading-progress.marko +36 -0
  237. package/src/theme/default/components/search.marko +239 -0
  238. package/src/theme/default/components/sidebar.marko +211 -0
  239. package/src/theme/default/components/site-footer.marko +211 -0
  240. package/src/theme/default/components/skip-link.marko +49 -0
  241. package/src/theme/default/components/theme/theme-aside-bottom.marko +1 -0
  242. package/src/theme/default/components/theme/theme-aside-top.marko +1 -0
  243. package/src/theme/default/components/theme/theme-body-bottom.marko +1 -0
  244. package/src/theme/default/components/theme/theme-body-top.marko +1 -0
  245. package/src/theme/default/components/theme/theme-doc-bottom.marko +1 -0
  246. package/src/theme/default/components/theme/theme-doc-footer-after.marko +1 -0
  247. package/src/theme/default/components/theme/theme-doc-footer-before.marko +1 -0
  248. package/src/theme/default/components/theme/theme-doc-top.marko +1 -0
  249. package/src/theme/default/components/theme/theme-head-bottom.marko +1 -0
  250. package/src/theme/default/components/theme/theme-head-top.marko +1 -0
  251. package/src/theme/default/components/theme/theme-home-features-after.marko +1 -0
  252. package/src/theme/default/components/theme/theme-home-hero-after.marko +1 -0
  253. package/src/theme/default/components/theme/theme-home-hero-before.marko +1 -0
  254. package/src/theme/default/components/theme/theme-navbar-center.marko +5 -0
  255. package/src/theme/default/components/theme/theme-navbar-end.marko +30 -0
  256. package/src/theme/default/components/theme/theme-navbar-start.marko +1 -0
  257. package/src/theme/default/components/theme/theme-page-bottom.marko +1 -0
  258. package/src/theme/default/components/theme/theme-page-top.marko +1 -0
  259. package/src/theme/default/components/theme/theme-sidebar-bottom.marko +1 -0
  260. package/src/theme/default/components/theme/theme-sidebar-top.marko +1 -0
  261. package/src/theme/default/components/theme/theme-toc-item.marko +1 -0
  262. package/src/theme/default/components/theme-toggle.marko +122 -0
  263. package/src/theme/default/components/toc.marko +140 -0
  264. package/src/theme/default/design-systems/default.ts +331 -0
  265. package/src/theme/default/design-systems/docusaurus.ts +341 -0
  266. package/src/theme/default/design-systems/index.ts +67 -0
  267. package/src/theme/default/design-systems/rspress.ts +341 -0
  268. package/src/theme/default/design-systems/types.ts +296 -0
  269. package/src/theme/default/design-systems/vitepress.ts +341 -0
  270. package/src/theme/default/index.ts +107 -0
  271. package/src/theme/default/layouts/blog.marko +65 -0
  272. package/src/theme/default/layouts/content-page.marko +41 -0
  273. package/src/theme/default/layouts/default.marko +209 -0
  274. package/src/theme/default/layouts/docs.marko +81 -0
  275. package/src/theme/default/layouts/home-page.marko +19 -0
  276. package/src/theme/default/layouts/page.marko +51 -0
  277. package/src/theme/default/public/theme-default.css +1081 -0
  278. package/src/theme/default/public/theme-docusaurus.css +1081 -0
  279. package/src/theme/default/public/theme-vitepress.css +1081 -0
  280. package/src/theme/default/public/theme.css +2 -0
  281. package/src/theme/default/routes/+layout.marko +57 -0
  282. package/src/theme/default/styles/main.css +249 -0
  283. package/src/theme/default/styles-base.css +757 -0
  284. package/src/theme/default/styles.css +899 -0
  285. package/src/theme/default/taglib.json +18 -0
  286. package/src/theme/default/tags/doc-footer.marko +180 -0
  287. package/src/theme/default/tags/footer.marko +32 -0
  288. package/src/theme/default/tags/header.marko +49 -0
  289. package/src/theme/default/tags/nav-bar.marko +191 -0
  290. package/src/theme/default/tags/page-header.marko +20 -0
  291. package/src/theme/default/tags/reading-progress.marko +36 -0
  292. package/src/theme/default/tags/search.marko +239 -0
  293. package/src/theme/default/tags/sidebar.marko +211 -0
  294. package/src/theme/default/tags/site-footer.marko +211 -0
  295. package/src/theme/default/tags/skip-link.marko +49 -0
  296. package/src/theme/default/tags/theme-aside-bottom.marko +1 -0
  297. package/src/theme/default/tags/theme-aside-top.marko +1 -0
  298. package/src/theme/default/tags/theme-body-bottom.marko +1 -0
  299. package/src/theme/default/tags/theme-body-top.marko +1 -0
  300. package/src/theme/default/tags/theme-doc-bottom.marko +1 -0
  301. package/src/theme/default/tags/theme-doc-footer-after.marko +1 -0
  302. package/src/theme/default/tags/theme-doc-footer-before.marko +1 -0
  303. package/src/theme/default/tags/theme-doc-top.marko +1 -0
  304. package/src/theme/default/tags/theme-head-bottom.marko +1 -0
  305. package/src/theme/default/tags/theme-head-top.marko +1 -0
  306. package/src/theme/default/tags/theme-home-features-after.marko +1 -0
  307. package/src/theme/default/tags/theme-home-hero-after.marko +1 -0
  308. package/src/theme/default/tags/theme-home-hero-before.marko +1 -0
  309. package/src/theme/default/tags/theme-navbar-center.marko +5 -0
  310. package/src/theme/default/tags/theme-navbar-end.marko +30 -0
  311. package/src/theme/default/tags/theme-navbar-start.marko +1 -0
  312. package/src/theme/default/tags/theme-page-bottom.marko +1 -0
  313. package/src/theme/default/tags/theme-page-top.marko +1 -0
  314. package/src/theme/default/tags/theme-sidebar-bottom.marko +1 -0
  315. package/src/theme/default/tags/theme-sidebar-top.marko +1 -0
  316. package/src/theme/default/tags/theme-toc-item.marko +1 -0
  317. package/src/theme/default/tags/theme-toggle.marko +122 -0
  318. package/src/theme/default/tags/toc.marko +140 -0
  319. package/src/theme/default/theme.ts +83 -0
  320. package/templates/blog-post.marko.template +13 -0
  321. package/templates/catch-all-handler.js.template +90 -0
  322. package/templates/catch-all-page.marko.template +69 -0
  323. package/templates/doc.marko.template +6 -0
  324. package/templates/example-tags/README.md +212 -0
  325. package/templates/example-tags/alert-box.marko +98 -0
  326. package/templates/example-tags/button-primary.marko +28 -0
  327. package/templates/example-tags/button-secondary.marko +28 -0
  328. package/templates/example-tags/button.marko +6 -0
  329. package/templates/example-tags/card-body.marko +8 -0
  330. package/templates/example-tags/card-footer.marko +7 -0
  331. package/templates/example-tags/card-header.marko +7 -0
  332. package/templates/example-tags/card.marko +20 -0
  333. package/templates/example-tags/icon.marko +149 -0
  334. package/templates/layout.marko.template +64 -0
  335. package/templates/page.marko.template +6 -0
@@ -0,0 +1,1553 @@
1
+ /**
2
+ * MarkoPress Build System
3
+ * Generates static HTML from markdown content
4
+ */
5
+ import { promises as fs } from 'node:fs';
6
+ import path from 'node:path';
7
+ import { spawn } from 'node:child_process';
8
+ import { fileURLToPath } from 'node:url';
9
+ import matter from 'gray-matter';
10
+ import { loadConfig } from '../config/loader.js';
11
+ import { getDesignSystem, getDarkModeOverride } from '../theme/default/design-systems/index.js';
12
+ import { globalTagValidator, formatValidationError } from '../markdown/tag-validator.js';
13
+ import { PluginManager } from '../plugin/manager.js';
14
+ import { loadMarkdownModule, registerMarkdownContent } from './vite-markdown-plugin.js';
15
+ const BUILD_MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
16
+ const MARKOPRESS_PACKAGE_ROOT = path.resolve(BUILD_MODULE_DIR, '..', '..');
17
+ const INTERNAL_DEFAULT_THEME_ROOT = path.join(MARKOPRESS_PACKAGE_ROOT, 'src', 'theme', 'default');
18
+ const INTERNAL_DEFAULT_THEME_NAMES = new Set([
19
+ '@markopress/theme-default',
20
+ 'theme-default',
21
+ 'default',
22
+ ]);
23
+ function isInternalDefaultTheme(themeName) {
24
+ return INTERNAL_DEFAULT_THEME_NAMES.has(themeName);
25
+ }
26
+ /**
27
+ * Build the MarkoPress site for production
28
+ */
29
+ export async function build(options = {}) {
30
+ const { outDir, debug = false, useCatchAllRoutes, root: optionRoot } = options;
31
+ const root = optionRoot || process.cwd();
32
+ const errors = [];
33
+ const timings = new Map();
34
+ const startTimes = new Map();
35
+ /**
36
+ * Timing helper - tracks elapsed time for build steps
37
+ * Uses performance.now() for high-resolution timing
38
+ */
39
+ const time = (label) => ({
40
+ start: () => {
41
+ startTimes.set(label, performance.now());
42
+ },
43
+ end: () => {
44
+ const start = startTimes.get(label) || 0;
45
+ const elapsed = performance.now() - start;
46
+ timings.set(label, elapsed);
47
+ },
48
+ });
49
+ try {
50
+ console.log('🚀 Building MarkoPress site...\n');
51
+ // Step 0: Load configuration
52
+ const t0 = time('Config loading');
53
+ t0.start();
54
+ const config = await loadConfig(root, { mode: 'production', command: 'build' });
55
+ t0.end();
56
+ // Step 1: Initialize plugin manager
57
+ let pluginManager;
58
+ if (config.plugins && config.plugins.length > 0) {
59
+ console.log('🔌 Loading plugins...');
60
+ const t1 = time('Plugin loading');
61
+ t1.start();
62
+ pluginManager = new PluginManager(config);
63
+ await pluginManager.loadPlugins(config.plugins);
64
+ t1.end();
65
+ console.log('');
66
+ }
67
+ // Step 2: Execute loadContent hooks (for backward compatibility)
68
+ if (pluginManager) {
69
+ console.log('📦 Loading plugin content...');
70
+ const t2 = time('Plugin loadContent hooks');
71
+ t2.start();
72
+ await pluginManager.execLoadContentHooks();
73
+ t2.end();
74
+ console.log(' Plugin content loaded\n');
75
+ }
76
+ // Step 3: Create ContentModule objects from content configuration
77
+ // This allows plugins to enhance modules with metadata (sidebar, toc, etc.)
78
+ const manifest = {};
79
+ const modules = [];
80
+ for (const [moduleId, contentConfig] of Object.entries(config.content)) {
81
+ const moduleConfig = typeof contentConfig === 'string'
82
+ ? { dir: contentConfig }
83
+ : contentConfig;
84
+ if (!moduleConfig?.dir)
85
+ continue;
86
+ // Create a minimal ContentModule object with enhancement support
87
+ const enhancements = new Map();
88
+ const module = {
89
+ id: moduleId,
90
+ dir: moduleConfig.dir,
91
+ config: moduleConfig,
92
+ // Plugin enhancement API
93
+ enhance(key, data) {
94
+ enhancements.set(key, data);
95
+ },
96
+ getEnhancement(key) {
97
+ return enhancements.get(key);
98
+ },
99
+ // Will be populated with file data during build
100
+ files: [],
101
+ // Store enhancements for serialization
102
+ _enhancements: enhancements,
103
+ };
104
+ // Scan content directory for markdown files
105
+ const contentDir = path.resolve(root, moduleConfig.dir);
106
+ try {
107
+ const entries = await fs.readdir(contentDir, { withFileTypes: true });
108
+ for (const entry of entries) {
109
+ if (entry.isFile() && entry.name.endsWith('.md')) {
110
+ const filePath = path.join(contentDir, entry.name);
111
+ const content = await fs.readFile(filePath, 'utf-8');
112
+ // Parse frontmatter with gray-matter so quoted strings are normalized.
113
+ let frontmatter = {};
114
+ try {
115
+ frontmatter = matter(content).data;
116
+ }
117
+ catch {
118
+ // Ignore parse errors and continue with empty metadata.
119
+ }
120
+ module.files.push({
121
+ id: entry.name.replace('.md', ''),
122
+ slug: entry.name.replace('.md', ''),
123
+ filePath,
124
+ urlPath: entry.name.replace('.md', '') === 'index' ? `/${moduleId}` : `/${moduleId}/${entry.name.replace('.md', '')}`,
125
+ processed: {
126
+ frontmatter,
127
+ },
128
+ });
129
+ }
130
+ }
131
+ }
132
+ catch {
133
+ // Directory doesn't exist, skip
134
+ }
135
+ modules.push(module);
136
+ }
137
+ // Step 4: Execute enhanceModules hooks
138
+ // This lets plugins like sidenav enhance modules with sidebar data
139
+ if (pluginManager && modules.length > 0) {
140
+ console.log('🔌 Enhancing modules with plugin metadata...');
141
+ const t2a = time('Module enhancement');
142
+ t2a.start();
143
+ // Debug: Log modules and their files
144
+ for (const module of modules) {
145
+ console.log(` Module: ${module.id} (${module.files.length} files)`);
146
+ if (module.files.length > 0) {
147
+ console.log(` First file has processed: ${!!module.files[0].processed}`);
148
+ }
149
+ }
150
+ await pluginManager.execEnhanceModulesHooks(modules);
151
+ t2a.end();
152
+ console.log(` Enhanced ${modules.length} module(s)\n`);
153
+ // Write module enhancements to a JSON file for request-time access
154
+ const generatedDir = path.join(root, 'src', '.generated');
155
+ await fs.mkdir(generatedDir, { recursive: true });
156
+ const moduleEnhancements = {};
157
+ for (const module of modules) {
158
+ const enhancements = {};
159
+ const entries = module._enhancements.entries();
160
+ for (const [key, value] of entries) {
161
+ enhancements[key] = value;
162
+ }
163
+ if (Object.keys(enhancements).length > 0) {
164
+ moduleEnhancements[module.id] = enhancements;
165
+ }
166
+ }
167
+ const enhancementsPath = path.join(generatedDir, 'module-enhancements.json');
168
+ await fs.writeFile(enhancementsPath, JSON.stringify(moduleEnhancements, null, 2), 'utf-8');
169
+ console.log(` Wrote module enhancements to src/.generated/module-enhancements.json\n`);
170
+ }
171
+ // Step 5: Initialize tag validator if Marko tags enabled
172
+ // Step 4: Initialize tag validator if Marko tags enabled
173
+ if (config.markdown.markoTags?.enabled) {
174
+ const tagsDir = path.join(root, config.markdown.markoTags.tagsDir || 'src/.markopress/tags');
175
+ console.log('🔍 Scanning tags directory...');
176
+ const t7 = time('Tag validation setup');
177
+ t7.start();
178
+ await globalTagValidator.loadAvailableTags(tagsDir);
179
+ t7.end();
180
+ console.log(` Found ${globalTagValidator.getAvailableTagsCount()} tags\n`);
181
+ }
182
+ else {
183
+ globalTagValidator.reset();
184
+ }
185
+ // Step 5: Ensure routes directory exists
186
+ // Note: Routes must be in src/routes/ for @marko/run compatibility
187
+ const routesDir = path.join(root, 'src', 'routes');
188
+ await fs.mkdir(routesDir, { recursive: true });
189
+ // Step 6: Initialize empty route manifest for plugins to extend
190
+ let routeManifest = {};
191
+ // Step 7: Execute extendRoutes hooks
192
+ if (pluginManager) {
193
+ const t5 = time('Extend routes hooks');
194
+ t5.start();
195
+ routeManifest = await pluginManager.execExtendRoutesHooks(routeManifest);
196
+ t5.end();
197
+ console.log('🔌 Extended routes manifest:', Object.keys(routeManifest).length);
198
+ }
199
+ // Step 8: Generate routes for content
200
+ console.log('📝 Generating routes from content...');
201
+ const t6 = time('Route generation');
202
+ t6.start();
203
+ const routeMode = useCatchAllRoutes ?? config.build.useCatchAllRoutes;
204
+ if (routeMode) {
205
+ await generateCatchAllRoutes(manifest, routesDir, config, modules, debug);
206
+ console.log(' Using catch-all dynamic routes');
207
+ }
208
+ else {
209
+ await generateRoutes(manifest, routesDir, config, modules, debug);
210
+ console.log(' Using static routes');
211
+ }
212
+ t6.end();
213
+ console.log(' Routes generated\n');
214
+ // Step 9: Convert routeManifest entries with handler/component to plugin routes
215
+ // This allows plugins to add custom routes via extendRoutes hook
216
+ const manifestRoutes = [];
217
+ for (const [path, route] of Object.entries(routeManifest)) {
218
+ if (route.handler || route.component) {
219
+ manifestRoutes.push({ path, ...route });
220
+ console.log(` Found plugin route: ${path}`);
221
+ }
222
+ }
223
+ console.log(`🔌 Total manifest routes: ${Object.keys(routeManifest).length}, Plugin routes: ${manifestRoutes.length}`);
224
+ // Step 10: Generate plugin routes
225
+ if (pluginManager) {
226
+ const pluginRoutes = pluginManager.getPluginRoutes();
227
+ const allPluginRoutes = [...pluginRoutes, ...manifestRoutes];
228
+ if (allPluginRoutes.length > 0) {
229
+ console.log(`🔌 Generating ${allPluginRoutes.length} plugin routes...`);
230
+ const t7 = time('Plugin route generation');
231
+ t7.start();
232
+ await generatePluginRoutes(allPluginRoutes, routesDir, config, debug);
233
+ t7.end();
234
+ console.log(' Plugin routes generated\n');
235
+ }
236
+ }
237
+ // Step 11: Generate Vite config for markdown content plugin
238
+ console.log('⚙️ Generating Vite config...');
239
+ const t10 = time('Vite config generation');
240
+ t10.start();
241
+ await generateViteConfig(root, debug);
242
+ t10.end();
243
+ console.log(' Vite config generated\n');
244
+ // Step 12: Execute allContentLoaded hooks
245
+ if (pluginManager) {
246
+ console.log('🔌 Processing plugin allContentLoaded hooks...');
247
+ const t8 = time('AllContentLoaded hooks');
248
+ t8.start();
249
+ await pluginManager.execAllContentLoadedHooks(routeManifest);
250
+ t8.end();
251
+ console.log(' All content processed\n');
252
+ }
253
+ // Step 12: Validate Marko tags if enabled
254
+ if (config.markdown.markoTags?.enabled) {
255
+ console.log('🔍 Validating Marko tags...');
256
+ const t9 = time('Tag validation');
257
+ t9.start();
258
+ const validation = globalTagValidator.validate();
259
+ t9.end();
260
+ if (!validation.success) {
261
+ const errorMessage = formatValidationError(validation.missingTags);
262
+ console.error(`\n${errorMessage}\n`);
263
+ errors.push(errorMessage);
264
+ return {
265
+ success: false,
266
+ outDir: '',
267
+ pages: 0,
268
+ errors,
269
+ };
270
+ }
271
+ console.log(' All tags validated ✓\n');
272
+ }
273
+ // Step 13: Copy theme CSS to public directory
274
+ console.log('🎨 Copying theme CSS...');
275
+ const t11 = time('Theme CSS copy');
276
+ t11.start();
277
+ await copyThemeCSS(root, config, debug);
278
+ t11.end();
279
+ console.log(' Theme CSS copied\n');
280
+ // Step 14: Extract styles from user-defined Marko components
281
+ console.log('🎨 Extracting styles from Marko components...');
282
+ const t12 = time('Marko component styles extraction');
283
+ t12.start();
284
+ await extractStylesFromMarkoTags(root, config, debug);
285
+ t12.end();
286
+ console.log(' Component styles extracted\n');
287
+ // Step 15: Theme components are auto-discovered from the markopress package
288
+ // via marko metadata (marko.json + package exports)
289
+ // console.log('📦 Copying theme components...');
290
+ // const t13 = time('Theme components copy');
291
+ // t13.start();
292
+ // await copyThemeComponents(root, config, debug);
293
+ // t13.end();
294
+ // console.log(' Theme components copied\n');
295
+ // Step 16: Build with @marko/run
296
+ console.log('🔨 Building with @marko/run...');
297
+ const t14 = time('@marko/run build');
298
+ t14.start();
299
+ const resolvedOutDir = outDir || config.build.outDir;
300
+ const buildResult = await runMarkoRunBuild(resolvedOutDir, debug, root);
301
+ t14.end();
302
+ if (!buildResult.success) {
303
+ errors.push(...buildResult.errors);
304
+ return {
305
+ success: false,
306
+ outDir: '',
307
+ pages: 0,
308
+ errors,
309
+ };
310
+ }
311
+ // Step 17: Collect build assets
312
+ const t15 = time('Collect build assets');
313
+ t15.start();
314
+ const assets = await collectBuildAssets(buildResult.outDir);
315
+ t15.end();
316
+ // Step 18: Execute postBuild hooks
317
+ if (pluginManager) {
318
+ console.log('🔌 Processing plugin postBuild hooks...');
319
+ const t16 = time('Post-build hooks');
320
+ t16.start();
321
+ await pluginManager.execPostBuildHooks(buildResult.outDir, routeManifest, assets);
322
+ t16.end();
323
+ console.log(' Post-build hooks completed\n');
324
+ }
325
+ // Step 19: Copy Marko tags directory to output (after build so it doesn't get cleaned)
326
+ console.log('📦 Copying Marko tags directory...');
327
+ const t18 = time('Copy tags directory');
328
+ t18.start();
329
+ await copyTagsDirectory(root, buildResult.outDir, config, debug);
330
+ t18.end();
331
+ console.log(' Tags directory copied\n');
332
+ console.log('\n✅ Build completed successfully!');
333
+ console.log(` Output: ${buildResult.outDir}`);
334
+ console.log(` Pages: Generated dynamically at request time`);
335
+ // Print timing summary
336
+ console.log('\n⏱️ Build timing:');
337
+ const sortedTimings = Array.from(timings.entries()).sort((a, b) => b[1] - a[1]);
338
+ for (const [label, time] of sortedTimings) {
339
+ const seconds = (time / 1000).toFixed(2);
340
+ const bar = '█'.repeat(Math.min(Math.floor(time / 100), 20));
341
+ console.log(` ${bar} ${label}: ${seconds}s`);
342
+ }
343
+ return {
344
+ success: true,
345
+ outDir: buildResult.outDir,
346
+ pages: 0, // Pages generated dynamically at request time
347
+ errors,
348
+ };
349
+ }
350
+ catch (error) {
351
+ const errorMessage = error instanceof Error ? error.message : String(error);
352
+ errors.push(errorMessage);
353
+ console.error('\n❌ Build failed:', errorMessage);
354
+ return {
355
+ success: false,
356
+ outDir: '',
357
+ pages: 0,
358
+ errors,
359
+ };
360
+ }
361
+ }
362
+ /**
363
+ * Generate plugin-defined routes
364
+ */
365
+ async function generatePluginRoutes(routes, routesDir, config, debug) {
366
+ for (const route of routes) {
367
+ const routePath = route.path.slice(1); // Remove leading slash
368
+ const dir = path.join(routesDir, routePath, '+page');
369
+ await fs.mkdir(path.dirname(dir), { recursive: true });
370
+ // Generate handler if specified
371
+ if (route.handler) {
372
+ const handlerFile = path.join(path.dirname(dir), '+handler.js');
373
+ await fs.writeFile(handlerFile, route.handler);
374
+ }
375
+ // Generate page if component specified
376
+ if (route.component) {
377
+ const pageFile = dir + '.marko';
378
+ await fs.writeFile(pageFile, route.component);
379
+ }
380
+ if (debug) {
381
+ console.log(` Generated plugin route: ${route.path}`);
382
+ }
383
+ }
384
+ }
385
+ /**
386
+ * Collect build assets from output directory
387
+ */
388
+ async function collectBuildAssets(outDir) {
389
+ const assets = [];
390
+ try {
391
+ const files = await fs.readdir(outDir, { recursive: true });
392
+ for (const file of files) {
393
+ if (typeof file === 'string' && (file.endsWith('.js') || file.endsWith('.css') || file.endsWith('.json'))) {
394
+ assets.push(file);
395
+ }
396
+ }
397
+ }
398
+ catch (error) {
399
+ // If directory doesn't exist or can't be read, return empty array
400
+ console.warn('Warning: Could not collect build assets:', error);
401
+ }
402
+ return assets;
403
+ }
404
+ /**
405
+ * Generate route files from content manifest
406
+ * Works with dynamic manifest structure
407
+ */
408
+ export async function generateRoutes(manifest, routesDir, config, modules, debug) {
409
+ // Clean up old generated routes safely
410
+ // Only delete files in directories we manage, preserving user's custom files
411
+ await cleanupGeneratedRoutes(routesDir, manifest, debug);
412
+ // Collect module IDs from manifest for cleanup
413
+ const moduleIds = [];
414
+ let pageCount = 0;
415
+ let docCount = 0;
416
+ let blogCount = 0;
417
+ // Generate routes for each module dynamically
418
+ for (const [moduleId, files] of Object.entries(manifest)) {
419
+ // Skip non-array entries
420
+ if (!Array.isArray(files))
421
+ continue;
422
+ moduleIds.push(moduleId);
423
+ const contentFiles = files;
424
+ if (moduleId === 'pages') {
425
+ // Generate static page routes (root-level, no prefix)
426
+ for (const page of contentFiles) {
427
+ await generatePageRoute(page, routesDir, config, modules, debug);
428
+ pageCount++;
429
+ }
430
+ }
431
+ else if (moduleId === 'blog') {
432
+ // Generate individual blog routes
433
+ for (const post of contentFiles) {
434
+ await generateBlogRoute(post, routesDir, config, modules, debug);
435
+ blogCount++;
436
+ }
437
+ }
438
+ else {
439
+ // Generate individual doc routes for all other modules
440
+ for (const doc of contentFiles) {
441
+ await generateDocRoute(doc, routesDir, config, modules, debug);
442
+ docCount++;
443
+ }
444
+ }
445
+ }
446
+ // Generate config file for handlers
447
+ await generateConfigFile(routesDir, config, debug);
448
+ // Generate vite.config.js for markdown content virtual module support
449
+ await generateViteConfig(config.root, debug);
450
+ // Generate root layout that wraps all pages with <${input.content}/>
451
+ await generateRootLayout(routesDir, config, debug);
452
+ if (debug) {
453
+ console.log(` Generated ${pageCount} page routes`);
454
+ console.log(` Generated ${docCount} doc routes`);
455
+ console.log(` Generated ${blogCount} blog routes`);
456
+ }
457
+ }
458
+ /**
459
+ * Safely clean up generated routes without deleting user files
460
+ *
461
+ * Security: Only deletes files in specific directories we manage.
462
+ * Preserves user's custom route handlers, middleware, and components.
463
+ *
464
+ * @param routesDir - Root routes directory
465
+ * @param manifest - Content manifest to determine what to keep
466
+ * @param debug - Enable debug logging
467
+ */
468
+ export async function cleanupGeneratedRoutes(routesDir, manifest, debug) {
469
+ const errors = [];
470
+ // Files that should NEVER be deleted (user's custom files)
471
+ const PRESERVE_PATTERNS = [
472
+ '+layout.marko', // Root layout
473
+ '+middleware.js', // Custom middleware
474
+ 'components/**/*', // User's components
475
+ 'api/**/*', // User's API routes
476
+ 'lib/**/*', // User's utilities
477
+ ];
478
+ // Build dynamic managed prefixes from manifest
479
+ // Skip 'pages' (root-level, no directory prefix)
480
+ const MANAGED_PREFIXES = Object.keys(manifest)
481
+ .filter(key => key !== 'pages')
482
+ .map(key => `${key}/`);
483
+ try {
484
+ const allFiles = await fs.readdir(routesDir, {
485
+ recursive: true,
486
+ withFileTypes: true,
487
+ });
488
+ for (const entry of allFiles) {
489
+ if (!entry.isFile())
490
+ continue;
491
+ // Build full path
492
+ const fullPath = path.join(entry.path || entry.parentPath || routesDir, entry.name);
493
+ const relativePath = path.relative(routesDir, fullPath);
494
+ // Check if file is in a managed directory
495
+ const isInManagedDir = MANAGED_PREFIXES.some((prefix) => relativePath.startsWith(prefix));
496
+ if (!isInManagedDir) {
497
+ // Not in a directory we manage, skip
498
+ continue;
499
+ }
500
+ // Check if file matches preserve patterns
501
+ const shouldPreserve = PRESERVE_PATTERNS.some((pattern) => {
502
+ if (pattern.includes('**')) {
503
+ // Glob pattern matching
504
+ const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'));
505
+ return regex.test(relativePath);
506
+ }
507
+ return entry.name === pattern;
508
+ });
509
+ if (shouldPreserve) {
510
+ if (debug)
511
+ console.log(` Preserving: ${relativePath}`);
512
+ continue;
513
+ }
514
+ // Safe to delete this generated file
515
+ try {
516
+ await fs.unlink(fullPath);
517
+ if (debug)
518
+ console.log(` Deleted: ${relativePath}`);
519
+ }
520
+ catch (error) {
521
+ if (error.code !== 'ENOENT') {
522
+ // Only report non-"file not found" errors
523
+ const errorMessage = error instanceof Error ? error.message : String(error);
524
+ errors.push(`Failed to delete ${relativePath}: ${errorMessage}`);
525
+ }
526
+ }
527
+ }
528
+ // Clean up empty directories in managed paths
529
+ for (const prefix of MANAGED_PREFIXES) {
530
+ const dirPath = path.join(routesDir, prefix);
531
+ try {
532
+ await cleanupEmptyDirectories(dirPath);
533
+ }
534
+ catch {
535
+ // Directory might not exist, that's okay
536
+ }
537
+ }
538
+ if (errors.length > 0) {
539
+ console.warn('⚠️ Cleanup warnings:');
540
+ errors.forEach((err) => console.warn(` ${err}`));
541
+ }
542
+ }
543
+ catch (error) {
544
+ // Routes directory might not exist yet, that's okay for initial build
545
+ if (error.code !== 'ENOENT') {
546
+ throw error;
547
+ }
548
+ }
549
+ }
550
+ /**
551
+ * Recursively remove empty directories
552
+ * @param dirPath - Directory to clean up
553
+ */
554
+ async function cleanupEmptyDirectories(dirPath) {
555
+ try {
556
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
557
+ // Recursively clean subdirectories first
558
+ for (const entry of entries) {
559
+ if (entry.isDirectory()) {
560
+ const subDirPath = path.join(dirPath, entry.name);
561
+ await cleanupEmptyDirectories(subDirPath);
562
+ }
563
+ }
564
+ // Check if directory is now empty
565
+ const updatedEntries = await fs.readdir(dirPath);
566
+ if (updatedEntries.length === 0) {
567
+ await fs.rmdir(dirPath);
568
+ }
569
+ }
570
+ catch {
571
+ // Ignore errors (directory might not exist or not be empty)
572
+ }
573
+ }
574
+ /**
575
+ * Generate a static page route
576
+ * NOTE: This function is deprecated in favor of catch-all routes with request-time rendering.
577
+ * Kept for backward compatibility with useCatchAllRoutes: false
578
+ */
579
+ async function generatePageRoute(page, routesDir, config, modules, debug) {
580
+ // Static routes are deprecated - use catch-all routes instead
581
+ if (debug) {
582
+ console.log(` Warning: Static routes deprecated, use catch-all routes`);
583
+ }
584
+ }
585
+ /**
586
+ * Generate a single documentation route
587
+ * NOTE: This function is deprecated in favor of catch-all routes with request-time rendering.
588
+ * Kept for backward compatibility with useCatchAllRoutes: false
589
+ */
590
+ async function generateDocRoute(doc, routesDir, config, modules, debug) {
591
+ // Static routes are deprecated - use catch-all routes instead
592
+ if (debug) {
593
+ console.log(` Warning: Static routes deprecated, use catch-all routes`);
594
+ }
595
+ }
596
+ /**
597
+ * Generate a single blog route
598
+ * NOTE: This function is deprecated in favor of catch-all routes with request-time rendering.
599
+ * Kept for backward compatibility with useCatchAllRoutes: false
600
+ */
601
+ async function generateBlogRoute(post, routesDir, config, modules, debug) {
602
+ // Static routes are deprecated - use catch-all routes instead
603
+ if (debug) {
604
+ console.log(` Warning: Static routes deprecated, use catch-all routes`);
605
+ }
606
+ }
607
+ /**
608
+ * Generate config file for handlers to import
609
+ * This makes theme options (navbar, footer, etc.) available to route handlers
610
+ */
611
+ async function generateConfigFile(routesDir, config, debug) {
612
+ const configFile = path.join(routesDir, '_config.js');
613
+ // Export the full config (except root which we add separately)
614
+ // Include all fields needed by handlers and templates
615
+ const handlerConfig = {
616
+ root: config.root,
617
+ site: {
618
+ title: config.site?.title || 'MarkoPress',
619
+ description: config.site?.description || '',
620
+ lang: config.site?.lang || 'en-US',
621
+ head: config.site?.head || [],
622
+ },
623
+ content: config.content,
624
+ theme: {
625
+ name: config.theme?.name || '@markopress/theme-default',
626
+ options: config.theme?.options || {},
627
+ },
628
+ markdown: config.markdown || {},
629
+ build: config.build || {},
630
+ };
631
+ // Export as ES module
632
+ const content = `// Auto-generated by MarkoPress - Do not edit
633
+ export const config = ${JSON.stringify(handlerConfig, null, 2)};
634
+ `;
635
+ await fs.writeFile(configFile, content);
636
+ if (debug) {
637
+ console.log(` Generated: ${configFile}`);
638
+ }
639
+ }
640
+ /**
641
+ * Generate root layout
642
+ */
643
+ async function generateRootLayout(routesDir, config, debug) {
644
+ const layoutFile = path.join(routesDir, '+layout.marko');
645
+ // Get navbar settings from config
646
+ const navbar = config.theme?.options?.navbar || [];
647
+ const siteTitle = config.site?.title || 'MarkoPress';
648
+ // Get theme style (default, vitepress, or docusaurus)
649
+ const themeStyle = config.theme?.options?.style || 'default';
650
+ // Generate layout using template file
651
+ const template = await loadTemplate('layout.marko.template', {
652
+ SITE_TITLE: siteTitle,
653
+ THEME_STYLE: themeStyle,
654
+ });
655
+ await fs.writeFile(layoutFile, template);
656
+ if (debug) {
657
+ console.log(` Generated: ${layoutFile}`);
658
+ }
659
+ }
660
+ /**
661
+ * Load a template file and replace placeholders
662
+ */
663
+ async function loadTemplate(templateName, replacements) {
664
+ // Template files are in the templates/ directory at package root
665
+ // When running from dist/, go up one level to find templates/
666
+ const distDir = path.dirname(new URL(import.meta.url).pathname);
667
+ const packageRoot = path.join(distDir, '..', '..');
668
+ const templatePath = path.join(packageRoot, 'templates', templateName);
669
+ let template = await fs.readFile(templatePath, 'utf-8');
670
+ // Replace all placeholders
671
+ for (const [key, value] of Object.entries(replacements)) {
672
+ template = template.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
673
+ }
674
+ return template;
675
+ }
676
+ /**
677
+ * Validate theme name to prevent path traversal attacks
678
+ *
679
+ * @param name - Theme package name to validate
680
+ * @throws {Error} If theme name is invalid or contains path traversal
681
+ *
682
+ * Security: Only allows valid npm package names:
683
+ * - Scoped: @scope/package-name (forward slash allowed after @)
684
+ * - Unscoped: package-name
685
+ * - Characters: lowercase letters, numbers, hyphens, dots, underscores
686
+ */
687
+ export function validateThemeName(name) {
688
+ // Valid npm package name pattern (scoped or unscoped)
689
+ // Allows: @scope/package, package-name, @org/my-package
690
+ const validPackageRegex = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
691
+ if (!validPackageRegex.test(name)) {
692
+ throw new Error(`Invalid theme name: "${name}". Must be a valid npm package name ` +
693
+ `(e.g., "my-theme" or "@org/my-theme")`);
694
+ }
695
+ // Explicitly block path traversal attempts
696
+ if (name.includes('..')) {
697
+ throw new Error(`Theme name cannot contain traversal sequences (..): "${name}"`);
698
+ }
699
+ // Block backslashes (Windows path separator)
700
+ if (name.includes('\\')) {
701
+ throw new Error(`Theme name cannot contain backslashes: "${name}"`);
702
+ }
703
+ // Block multiple forward slashes (only one allowed in scoped packages)
704
+ const slashCount = (name.match(/\//g) || []).length;
705
+ if (slashCount > 1) {
706
+ throw new Error(`Theme name can only contain one forward slash (for scoped packages): "${name}"`);
707
+ }
708
+ // Block absolute paths (starts with / on Unix or C:\ on Windows)
709
+ if (path.isAbsolute(name)) {
710
+ throw new Error(`Theme name cannot be an absolute path: "${name}"`);
711
+ }
712
+ }
713
+ /**
714
+ * Generate Vite config that extends @marko/run with markdown content plugin
715
+ */
716
+ export async function generateViteConfig(rootDir, debug) {
717
+ const viteConfigPath = path.join(rootDir, 'vite.config.js');
718
+ const viteConfigContent = `import { defineConfig } from 'vite';
719
+ import marko from '@marko/run/vite';
720
+ import { markdownContentPlugin } from 'markopress/build';
721
+
722
+ export default defineConfig({
723
+ plugins: [
724
+ marko(),
725
+ markdownContentPlugin(),
726
+ ],
727
+ resolve: {
728
+ // Preserve symlinks for pnpm workspace compatibility
729
+ // This allows Marko to properly discover tags from symlinked packages
730
+ preserveSymlinks: true,
731
+ },
732
+ build: {
733
+ outDir: 'dist',
734
+ },
735
+ });
736
+ `;
737
+ try {
738
+ // Check if file already exists and has our plugin
739
+ const existing = await fs.readFile(viteConfigPath, 'utf-8');
740
+ if (existing.includes('markdownContentPlugin')) {
741
+ if (debug) {
742
+ console.log(' Vite config already has markdownContentPlugin');
743
+ }
744
+ return;
745
+ }
746
+ }
747
+ catch {
748
+ // File doesn't exist, create it
749
+ }
750
+ await fs.writeFile(viteConfigPath, viteConfigContent);
751
+ if (debug) {
752
+ console.log(` Created vite.config.js with markdownContentPlugin`);
753
+ }
754
+ }
755
+ /**
756
+ * Copy theme CSS to public directory
757
+ */
758
+ export async function copyThemeCSS(rootDir, config, debug) {
759
+ // Create the _markopress/theme directory in public
760
+ const themeDir = path.join(rootDir, 'public', '_markopress', 'theme');
761
+ await fs.mkdir(themeDir, { recursive: true });
762
+ const themeName = config.theme?.name || '@markopress/theme-default';
763
+ // Security: Validate theme name to prevent path traversal
764
+ try {
765
+ validateThemeName(themeName);
766
+ }
767
+ catch (error) {
768
+ const errorMessage = error instanceof Error ? error.message : String(error);
769
+ throw new Error(`Security: ${errorMessage}`);
770
+ }
771
+ // Get the style from theme options
772
+ const style = config.theme?.options?.style || 'default';
773
+ const cssFileName = `theme-${style}.css`;
774
+ // Try multiple locations for the pre-generated theme CSS
775
+ const possiblePaths = [
776
+ ...(isInternalDefaultTheme(themeName)
777
+ ? [path.join(INTERNAL_DEFAULT_THEME_ROOT, 'public', cssFileName)]
778
+ : []),
779
+ // pnpm workspace: root node_modules
780
+ path.join(rootDir, '..', 'node_modules', themeName, 'public', cssFileName),
781
+ // Local node_modules
782
+ path.join(rootDir, 'node_modules', themeName, 'public', cssFileName),
783
+ ];
784
+ let themeCSS = null;
785
+ let foundPath = null;
786
+ for (const cssPath of possiblePaths) {
787
+ try {
788
+ await fs.access(cssPath);
789
+ themeCSS = await fs.readFile(cssPath, 'utf-8');
790
+ foundPath = cssPath;
791
+ break;
792
+ }
793
+ catch {
794
+ // Try next path
795
+ }
796
+ }
797
+ if (!themeCSS) {
798
+ // Fallback: create a minimal CSS file
799
+ console.warn(` Warning: Could not find ${cssFileName}, using minimal fallback`);
800
+ const fallbackCSS = `/* Minimal fallback CSS for style: ${style} */\nbody { font-family: system-ui, sans-serif; margin: 0; padding: 0; }`;
801
+ const outputPath = path.join(themeDir, cssFileName);
802
+ await fs.writeFile(outputPath, fallbackCSS);
803
+ return;
804
+ }
805
+ // Write the CSS file
806
+ const outputPath = path.join(themeDir, cssFileName);
807
+ await fs.writeFile(outputPath, themeCSS);
808
+ if (debug) {
809
+ console.log(` Copied ${cssFileName} from: ${foundPath}`);
810
+ console.log(` Output: ${outputPath}`);
811
+ }
812
+ // Also copy the component styles (styles.css) from the theme package
813
+ const stylesCSSFileName = 'styles.css';
814
+ const stylesPossiblePaths = [
815
+ ...(isInternalDefaultTheme(themeName)
816
+ ? [path.join(INTERNAL_DEFAULT_THEME_ROOT, stylesCSSFileName)]
817
+ : []),
818
+ path.join(rootDir, '..', 'node_modules', themeName, 'src', stylesCSSFileName),
819
+ path.join(rootDir, 'node_modules', themeName, 'src', stylesCSSFileName),
820
+ ];
821
+ for (const cssPath of stylesPossiblePaths) {
822
+ try {
823
+ await fs.access(cssPath);
824
+ const stylesCSS = await fs.readFile(cssPath, 'utf-8');
825
+ const stylesOutputPath = path.join(themeDir, stylesCSSFileName);
826
+ await fs.writeFile(stylesOutputPath, stylesCSS);
827
+ if (debug) {
828
+ console.log(` Copied ${stylesCSSFileName} from: ${cssPath}`);
829
+ console.log(` Output: ${stylesOutputPath}`);
830
+ }
831
+ break;
832
+ }
833
+ catch {
834
+ // Try next path
835
+ }
836
+ }
837
+ }
838
+ /**
839
+ * Extract styles from user-defined Marko components
840
+ * Scans the tags directory, extracts <style> blocks from .marko files,
841
+ * and combines them into a single CSS file for global loading
842
+ *
843
+ * This is necessary because request-time virtual markdown modules
844
+ * do not emit tag-local CSS assets reliably.
845
+ */
846
+ export async function extractStylesFromMarkoTags(rootDir, config, debug) {
847
+ const tagsDirConfig = config.markdown?.markoTags?.tagsDir || 'src/.markopress/tags';
848
+ const tagsDir = path.join(rootDir, tagsDirConfig);
849
+ // Check if tags directory exists
850
+ try {
851
+ await fs.access(tagsDir);
852
+ }
853
+ catch {
854
+ // Tags directory doesn't exist, skip extraction
855
+ if (debug) {
856
+ console.log(` No tags directory found at: ${tagsDir}`);
857
+ }
858
+ return;
859
+ }
860
+ // Collect all .marko files recursively
861
+ const markoFiles = [];
862
+ async function collectMarkoFiles(dir) {
863
+ const entries = await fs.readdir(dir, { withFileTypes: true });
864
+ for (const entry of entries) {
865
+ const fullPath = path.join(dir, entry.name);
866
+ if (entry.isDirectory()) {
867
+ await collectMarkoFiles(fullPath);
868
+ }
869
+ else if (entry.isFile() && entry.name.endsWith('.marko')) {
870
+ markoFiles.push(fullPath);
871
+ }
872
+ }
873
+ }
874
+ await collectMarkoFiles(tagsDir);
875
+ if (markoFiles.length === 0) {
876
+ if (debug) {
877
+ console.log(` No .marko files found in: ${tagsDir}`);
878
+ }
879
+ return;
880
+ }
881
+ // Extract styles from each .marko file
882
+ const cssEntries = [];
883
+ cssEntries.push('/* Custom markdown tag styles');
884
+ cssEntries.push(' * Loaded globally because request-time virtual markdown modules');
885
+ cssEntries.push(' * do not emit tag-local CSS assets reliably. */');
886
+ cssEntries.push('');
887
+ for (const filePath of markoFiles) {
888
+ const relativePath = path.relative(tagsDir, filePath);
889
+ const componentName = path.dirname(relativePath) === ''
890
+ ? path.basename(relativePath, '.marko')
891
+ : path.join(path.dirname(relativePath), path.basename(relativePath, '.marko'));
892
+ try {
893
+ const content = await fs.readFile(filePath, 'utf-8');
894
+ // Extract <style> blocks using regex
895
+ // Match both <style>...</style> and <style>...</style> with attributes
896
+ const styleRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
897
+ const matches = Array.from(content.matchAll(styleRegex));
898
+ if (matches.length > 0) {
899
+ cssEntries.push(`/* ${componentName}.marko */`);
900
+ for (const match of matches) {
901
+ const styleContent = match[1] || '';
902
+ if (styleContent) {
903
+ // Split into lines and trim leading/trailing empty lines
904
+ const lines = styleContent.split('\n');
905
+ // Find first non-empty line
906
+ let startIndex = 0;
907
+ while (startIndex < lines.length && lines[startIndex].trim() === '') {
908
+ startIndex++;
909
+ }
910
+ // Find last non-empty line
911
+ let endIndex = lines.length - 1;
912
+ while (endIndex >= startIndex && lines[endIndex].trim() === '') {
913
+ endIndex--;
914
+ }
915
+ // Process the non-empty content
916
+ for (let i = startIndex; i <= endIndex; i++) {
917
+ const line = lines[i];
918
+ // Preserve empty lines within the content
919
+ if (line.trim() === '') {
920
+ cssEntries.push('');
921
+ continue;
922
+ }
923
+ // Detect current indentation level and normalize to 2-space increments
924
+ const indentMatch = line.match(/^(\s*)/);
925
+ const currentIndent = indentMatch ? indentMatch[1].length : 0;
926
+ // Normalize: preserve relative indentation but normalize to 2-space base
927
+ // If source uses 4-space indent, convert to 2-space; if 2-space, keep as-is
928
+ const normalizedIndent = Math.floor(currentIndent / 2);
929
+ const newIndent = ' '.repeat(normalizedIndent);
930
+ const content = line.trim();
931
+ // Remove :global() wrappers - they're for CSS Modules, not global CSS
932
+ const strippedGlobal = content.replace(/:global\(([^)]+)\)/g, '$1');
933
+ cssEntries.push(newIndent + strippedGlobal);
934
+ }
935
+ }
936
+ }
937
+ cssEntries.push('');
938
+ }
939
+ }
940
+ catch (error) {
941
+ console.warn(` Warning: Could not read file ${filePath}:`, error);
942
+ }
943
+ }
944
+ // Create public directory if it doesn't exist
945
+ const publicDir = path.join(rootDir, 'public');
946
+ await fs.mkdir(publicDir, { recursive: true });
947
+ // Write the combined CSS file
948
+ const outputPath = path.join(publicDir, 'markopress-components.css');
949
+ const cssContent = cssEntries.join('\n');
950
+ await fs.writeFile(outputPath, cssContent);
951
+ if (debug) {
952
+ console.log(` Extracted styles from ${markoFiles.length} Marko component(s)`);
953
+ console.log(` Output: ${outputPath}`);
954
+ }
955
+ }
956
+ /**
957
+ * Copy theme components to routes/tags directory for Marko auto-discovery
958
+ * This makes theme components (like <theme-navbar-end>, <toc>, etc.) available
959
+ * without requiring explicit imports in templates
960
+ */
961
+ async function getAllMarkoFiles(dir) {
962
+ const dirents = await fs.readdir(dir, { withFileTypes: true });
963
+ const files = await Promise.all(dirents.map((dirent) => {
964
+ const res = path.resolve(dir, dirent.name);
965
+ return dirent.isDirectory() ? getAllMarkoFiles(res) : res;
966
+ }));
967
+ return Array.prototype.concat(...files).filter(file => file.endsWith('.marko'));
968
+ }
969
+ export async function copyThemeComponents(rootDir, config, debug) {
970
+ const themeName = config.theme?.name || '@markopress/theme-default';
971
+ // Target directory: src/tags/ where Marko can find from both routes and generated files
972
+ const srcDir = path.join(rootDir, 'src');
973
+ const tagsDir = path.join(srcDir, 'tags');
974
+ // Ensure tags directory exists
975
+ await fs.mkdir(tagsDir, { recursive: true });
976
+ // Possible locations for theme components
977
+ const possiblePaths = [
978
+ ...(isInternalDefaultTheme(themeName)
979
+ ? [path.join(INTERNAL_DEFAULT_THEME_ROOT, 'tags')]
980
+ : []),
981
+ // pnpm workspace: root node_modules (dist tags)
982
+ path.join(rootDir, '..', 'node_modules', themeName, 'dist', 'tags'),
983
+ // Local node_modules (dist tags)
984
+ path.join(rootDir, 'node_modules', themeName, 'dist', 'tags'),
985
+ // pnpm workspace: root node_modules (source components)
986
+ path.join(rootDir, '..', 'node_modules', themeName, 'src', 'components'),
987
+ // Local node_modules (source components)
988
+ path.join(rootDir, 'node_modules', themeName, 'src', 'components'),
989
+ ];
990
+ let sourceTagsDir = null;
991
+ for (const dirPath of possiblePaths) {
992
+ try {
993
+ // Check if directory exists
994
+ await fs.access(dirPath);
995
+ sourceTagsDir = dirPath;
996
+ break;
997
+ }
998
+ catch {
999
+ // Directory doesn't exist, continue
1000
+ }
1001
+ }
1002
+ if (!sourceTagsDir) {
1003
+ if (debug) {
1004
+ console.warn(` Warning: Could not find theme components, skipping`);
1005
+ }
1006
+ return;
1007
+ }
1008
+ // Get all .marko files recursively
1009
+ const allMarkoFiles = await getAllMarkoFiles(sourceTagsDir);
1010
+ let copiedCount = 0;
1011
+ for (const file of allMarkoFiles) {
1012
+ const relativePath = path.relative(sourceTagsDir, file);
1013
+ const destPath = path.join(tagsDir, relativePath);
1014
+ let exists = false;
1015
+ try {
1016
+ await fs.access(destPath);
1017
+ exists = true;
1018
+ }
1019
+ catch {
1020
+ // file does not exist
1021
+ }
1022
+ if (exists) {
1023
+ if (debug) {
1024
+ console.log(` Skipped component (user override exists): ${relativePath}`);
1025
+ }
1026
+ }
1027
+ else {
1028
+ // File doesn't exist, so copy it
1029
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
1030
+ await fs.copyFile(file, destPath);
1031
+ copiedCount++;
1032
+ }
1033
+ }
1034
+ if (debug) {
1035
+ console.log(` Copied ${copiedCount} theme components from: ${sourceTagsDir}`);
1036
+ console.log(` Output: ${tagsDir}`);
1037
+ }
1038
+ }
1039
+ /**
1040
+ * Copy Marko tags directory to output directory for component discovery
1041
+ * This allows Marko runtime to find and render custom components
1042
+ */
1043
+ export async function copyTagsDirectory(rootDir, outDir, config, debug) {
1044
+ const tagsDirConfig = config.markdown?.markoTags?.tagsDir || 'src/.markopress/tags';
1045
+ const tagsDir = path.join(rootDir, tagsDirConfig);
1046
+ const distTagsDir = path.join(outDir, 'tags');
1047
+ // Check if tags directory exists
1048
+ try {
1049
+ await fs.access(tagsDir);
1050
+ }
1051
+ catch {
1052
+ // Tags directory doesn't exist, skip copying
1053
+ if (debug) {
1054
+ console.log(` No tags directory found at: ${tagsDir}`);
1055
+ }
1056
+ return;
1057
+ }
1058
+ // Create destination directory
1059
+ await fs.mkdir(distTagsDir, { recursive: true });
1060
+ // Copy all tag files recursively
1061
+ const files = await fs.readdir(tagsDir, { withFileTypes: true });
1062
+ let copiedCount = 0;
1063
+ for (const file of files) {
1064
+ const srcPath = path.join(tagsDir, file.name);
1065
+ const destPath = path.join(distTagsDir, file.name);
1066
+ if (file.isDirectory()) {
1067
+ // Recursively copy subdirectories
1068
+ await fs.mkdir(destPath, { recursive: true });
1069
+ const subFiles = await fs.readdir(srcPath, { withFileTypes: true });
1070
+ for (const subFile of subFiles) {
1071
+ const subSrcPath = path.join(srcPath, subFile.name);
1072
+ const subDestPath = path.join(destPath, subFile.name);
1073
+ if (!subFile.isDirectory()) {
1074
+ await fs.copyFile(subSrcPath, subDestPath);
1075
+ copiedCount++;
1076
+ }
1077
+ }
1078
+ }
1079
+ else if (file.isFile()) {
1080
+ // Copy tag files (.marko, .js, .ts, etc.)
1081
+ await fs.copyFile(srcPath, destPath);
1082
+ copiedCount++;
1083
+ }
1084
+ }
1085
+ if (debug) {
1086
+ console.log(` Copied ${copiedCount} tag files from: ${tagsDir}`);
1087
+ console.log(` Output: ${distTagsDir}`);
1088
+ }
1089
+ }
1090
+ /**
1091
+ * Convert design system tokens to CSS variables
1092
+ */
1093
+ function designSystemToCSS(ds, darkMode = null) {
1094
+ const lines = [];
1095
+ // Start :root block
1096
+ lines.push(':root {');
1097
+ // Colors
1098
+ lines.push(' /* Brand/Primary Colors */');
1099
+ lines.push(` --color-primary-1: ${ds.colors.primary['1']};`);
1100
+ lines.push(` --color-primary-2: ${ds.colors.primary['2']};`);
1101
+ lines.push(` --color-primary-3: ${ds.colors.primary['3']};`);
1102
+ lines.push(` --color-primary-soft: ${ds.colors.primary.soft};`);
1103
+ lines.push('');
1104
+ lines.push(' /* Semantic Colors */');
1105
+ lines.push(` --color-success-1: ${ds.colors.success['1']};`);
1106
+ lines.push(` --color-success-2: ${ds.colors.success['2']};`);
1107
+ lines.push(` --color-success-3: ${ds.colors.success['3']};`);
1108
+ lines.push(` --color-warning-1: ${ds.colors.warning['1']};`);
1109
+ lines.push(` --color-warning-2: ${ds.colors.warning['2']};`);
1110
+ lines.push(` --color-warning-3: ${ds.colors.warning['3']};`);
1111
+ lines.push(` --color-danger-1: ${ds.colors.danger['1']};`);
1112
+ lines.push(` --color-danger-2: ${ds.colors.danger['2']};`);
1113
+ lines.push(` --color-danger-3: ${ds.colors.danger['3']};`);
1114
+ lines.push(` --color-info-1: ${ds.colors.info['1']};`);
1115
+ lines.push(` --color-info-2: ${ds.colors.info['2']};`);
1116
+ lines.push(` --color-info-3: ${ds.colors.info['3']};`);
1117
+ lines.push('');
1118
+ lines.push(' /* Gray Scale */');
1119
+ lines.push(` --color-gray-1: ${ds.colors.gray['1']};`);
1120
+ lines.push(` --color-gray-2: ${ds.colors.gray['2']};`);
1121
+ lines.push(` --color-gray-3: ${ds.colors.gray['3']};`);
1122
+ lines.push(` --color-gray-soft: ${ds.colors.gray.soft};`);
1123
+ lines.push('');
1124
+ lines.push(' /* Background Colors */');
1125
+ lines.push(` --bg-default: ${ds.colors.bg.default};`);
1126
+ lines.push(` --bg-alt: ${ds.colors.bg.alt};`);
1127
+ lines.push(` --bg-elevated: ${ds.colors.bg.elevated};`);
1128
+ lines.push(` --bg-soft: ${ds.colors.bg.soft};`);
1129
+ lines.push('');
1130
+ lines.push(' /* Text Colors */');
1131
+ lines.push(` --text-1: ${ds.colors.text['1']};`);
1132
+ lines.push(` --text-2: ${ds.colors.text['2']};`);
1133
+ lines.push(` --text-3: ${ds.colors.text['3']};`);
1134
+ lines.push('');
1135
+ lines.push(' /* Border Colors */');
1136
+ lines.push(` --border-default: ${ds.colors.border.default};`);
1137
+ lines.push(` --border-divider: ${ds.colors.border.divider};`);
1138
+ lines.push(` --border-gutter: ${ds.colors.border.gutter};`);
1139
+ lines.push('');
1140
+ lines.push(' /* Typography */');
1141
+ lines.push(` --font-sans: ${ds.typography.fontFamily.sans};`);
1142
+ lines.push(` --font-mono: ${ds.typography.fontFamily.mono};`);
1143
+ lines.push('');
1144
+ lines.push(' /* Font Sizes */');
1145
+ lines.push(` --font-size-xs: ${ds.typography.fontSize.xs};`);
1146
+ lines.push(` --font-size-sm: ${ds.typography.fontSize.sm};`);
1147
+ lines.push(` --font-size-base: ${ds.typography.fontSize.base};`);
1148
+ lines.push(` --font-size-lg: ${ds.typography.fontSize.lg};`);
1149
+ lines.push(` --font-size-xl: ${ds.typography.fontSize.xl};`);
1150
+ lines.push(` --font-size-2xl: ${ds.typography.fontSize['2xl']};`);
1151
+ lines.push(` --font-size-3xl: ${ds.typography.fontSize['3xl']};`);
1152
+ lines.push(` --font-size-4xl: ${ds.typography.fontSize['4xl']};`);
1153
+ lines.push('');
1154
+ lines.push(' /* Font Weights */');
1155
+ lines.push(` --font-weight-normal: ${ds.typography.fontWeight.normal};`);
1156
+ lines.push(` --font-weight-medium: ${ds.typography.fontWeight.medium};`);
1157
+ lines.push(` --font-weight-semibold: ${ds.typography.fontWeight.semibold};`);
1158
+ lines.push(` --font-weight-bold: ${ds.typography.fontWeight.bold};`);
1159
+ lines.push('');
1160
+ lines.push(' /* Line Heights */');
1161
+ lines.push(` --leading-tight: ${ds.typography.lineHeight.tight};`);
1162
+ lines.push(` --leading-normal: ${ds.typography.lineHeight.normal};`);
1163
+ lines.push(` --leading-relaxed: ${ds.typography.lineHeight.relaxed};`);
1164
+ lines.push('');
1165
+ lines.push(' /* Spacing */');
1166
+ lines.push(` --space-xs: ${ds.spacing.scale.xs};`);
1167
+ lines.push(` --space-sm: ${ds.spacing.scale.sm};`);
1168
+ lines.push(` --space-md: ${ds.spacing.scale.md};`);
1169
+ lines.push(` --space-lg: ${ds.spacing.scale.lg};`);
1170
+ lines.push(` --space-xl: ${ds.spacing.scale.xl};`);
1171
+ lines.push(` --space-2xl: ${ds.spacing.scale['2xl']};`);
1172
+ lines.push(` --space-3xl: ${ds.spacing.scale['3xl']};`);
1173
+ lines.push(` --space-4xl: ${ds.spacing.scale['4xl']};`);
1174
+ lines.push('');
1175
+ lines.push(' /* Shadows */');
1176
+ lines.push(` --shadow-1: ${ds.effects.shadows['1']};`);
1177
+ lines.push(` --shadow-2: ${ds.effects.shadows['2']};`);
1178
+ lines.push(` --shadow-3: ${ds.effects.shadows['3']};`);
1179
+ lines.push(` --shadow-4: ${ds.effects.shadows['4']};`);
1180
+ lines.push(` --shadow-5: ${ds.effects.shadows['5']};`);
1181
+ lines.push('');
1182
+ lines.push(' /* Border Radius */');
1183
+ lines.push(` --radius-sm: ${ds.effects.borderRadius.sm};`);
1184
+ lines.push(` --radius-md: ${ds.effects.borderRadius.md};`);
1185
+ lines.push(` --radius-lg: ${ds.effects.borderRadius.lg};`);
1186
+ lines.push('');
1187
+ lines.push(' /* Transitions */');
1188
+ if (ds.effects.transitions?.base)
1189
+ lines.push(` --transition-base: ${ds.effects.transitions.base};`);
1190
+ if (ds.effects.transitions?.fast)
1191
+ lines.push(` --transition-fast: ${ds.effects.transitions.fast};`);
1192
+ if (ds.effects.transitions?.slow)
1193
+ lines.push(` --transition-slow: ${ds.effects.transitions.slow};`);
1194
+ lines.push('');
1195
+ lines.push(' /* Layout */');
1196
+ lines.push(` --layout-max-width: ${ds.layout.maxWidth};`);
1197
+ lines.push(` --navbar-height: ${ds.layout.navbarHeight};`);
1198
+ lines.push(` --sidebar-width: ${ds.layout.sidebarWidth};`);
1199
+ if (ds.layout.tocWidth)
1200
+ lines.push(` --toc-width: ${ds.layout.tocWidth};`);
1201
+ lines.push('');
1202
+ lines.push(' /* Z-Index */');
1203
+ lines.push(` --z-footer: ${ds.layout.zIndex.footer};`);
1204
+ lines.push(` --z-local-nav: ${ds.layout.zIndex.localNav};`);
1205
+ lines.push(` --z-nav: ${ds.layout.zIndex.nav};`);
1206
+ lines.push(` --z-layout-top: ${ds.layout.zIndex.layoutTop};`);
1207
+ lines.push(` --z-backdrop: ${ds.layout.zIndex.backdrop};`);
1208
+ lines.push(` --z-sidebar: ${ds.layout.zIndex.sidebar};`);
1209
+ // Component-specific variables
1210
+ if (ds.components) {
1211
+ if (ds.components.navbar) {
1212
+ lines.push('');
1213
+ lines.push(' /* Navbar Component */');
1214
+ if (ds.components.navbar.height)
1215
+ lines.push(` --navbar-height: ${ds.components.navbar.height};`);
1216
+ if (ds.components.navbar.padding)
1217
+ lines.push(` --navbar-padding: ${ds.components.navbar.padding};`);
1218
+ if (ds.components.navbar.background)
1219
+ lines.push(` --navbar-background: ${ds.components.navbar.background};`);
1220
+ if (ds.components.navbar.border)
1221
+ lines.push(` --navbar-border: ${ds.components.navbar.border};`);
1222
+ if (ds.components.navbar.borderWidth)
1223
+ lines.push(` --navbar-border-width: ${ds.components.navbar.borderWidth};`);
1224
+ if (ds.components.navbar.shadow)
1225
+ lines.push(` --navbar-shadow: ${ds.components.navbar.shadow};`);
1226
+ if (ds.components.navbar.logoHeight)
1227
+ lines.push(` --navbar-logo-height: ${ds.components.navbar.logoHeight};`);
1228
+ if (ds.components.navbar.itemPadding)
1229
+ lines.push(` --navbar-item-padding: ${ds.components.navbar.itemPadding};`);
1230
+ if (ds.components.navbar.itemGap)
1231
+ lines.push(` --navbar-item-gap: ${ds.components.navbar.itemGap};`);
1232
+ }
1233
+ if (ds.components.sidebar) {
1234
+ lines.push('');
1235
+ lines.push(' /* Sidebar Component */');
1236
+ if (ds.components.sidebar.width)
1237
+ lines.push(` --sidebar-width: ${ds.components.sidebar.width};`);
1238
+ if (ds.components.sidebar.padding)
1239
+ lines.push(` --sidebar-padding: ${ds.components.sidebar.padding};`);
1240
+ if (ds.components.sidebar.background)
1241
+ lines.push(` --sidebar-background: ${ds.components.sidebar.background};`);
1242
+ if (ds.components.sidebar.border)
1243
+ lines.push(` --sidebar-border: ${ds.components.sidebar.border};`);
1244
+ if (ds.components.sidebar.itemHeight)
1245
+ lines.push(` --sidebar-item-height: ${ds.components.sidebar.itemHeight};`);
1246
+ if (ds.components.sidebar.itemPadding)
1247
+ lines.push(` --sidebar-item-padding: ${ds.components.sidebar.itemPadding};`);
1248
+ if (ds.components.sidebar.itemGap)
1249
+ lines.push(` --sidebar-item-gap: ${ds.components.sidebar.itemGap};`);
1250
+ if (ds.components.sidebar.itemBorderRadius)
1251
+ lines.push(` --sidebar-item-border-radius: ${ds.components.sidebar.itemBorderRadius};`);
1252
+ if (ds.components.sidebar.itemFontSize)
1253
+ lines.push(` --sidebar-item-font-size: ${ds.components.sidebar.itemFontSize};`);
1254
+ if (ds.components.sidebar.itemFontWeight)
1255
+ lines.push(` --sidebar-item-font-weight: ${ds.components.sidebar.itemFontWeight};`);
1256
+ if (ds.components.sidebar.activeBackground)
1257
+ lines.push(` --sidebar-active-background: ${ds.components.sidebar.activeBackground};`);
1258
+ if (ds.components.sidebar.activeBorder)
1259
+ lines.push(` --sidebar-active-border: ${ds.components.sidebar.activeBorder};`);
1260
+ if (ds.components.sidebar.hoverBackground)
1261
+ lines.push(` --sidebar-hover-background: ${ds.components.sidebar.hoverBackground};`);
1262
+ if (ds.components.sidebar.categoryPadding)
1263
+ lines.push(` --sidebar-category-padding: ${ds.components.sidebar.categoryPadding};`);
1264
+ if (ds.components.sidebar.categoryFontSize)
1265
+ lines.push(` --sidebar-category-font-size: ${ds.components.sidebar.categoryFontSize};`);
1266
+ if (ds.components.sidebar.categoryFontWeight)
1267
+ lines.push(` --sidebar-category-font-weight: ${ds.components.sidebar.categoryFontWeight};`);
1268
+ if (ds.components.sidebar.categoryTextTransform)
1269
+ lines.push(` --sidebar-category-text-transform: ${ds.components.sidebar.categoryTextTransform};`);
1270
+ }
1271
+ if (ds.components.content) {
1272
+ lines.push('');
1273
+ lines.push(' /* Content Component */');
1274
+ if (ds.components.content.maxWidth)
1275
+ lines.push(` --content-max-width: ${ds.components.content.maxWidth};`);
1276
+ if (ds.components.content.padding)
1277
+ lines.push(` --content-padding: ${ds.components.content.padding};`);
1278
+ if (ds.components.content.fontSize)
1279
+ lines.push(` --content-font-size: ${ds.components.content.fontSize};`);
1280
+ if (ds.components.content.lineHeight)
1281
+ lines.push(` --content-line-height: ${ds.components.content.lineHeight};`);
1282
+ }
1283
+ if (ds.components.code) {
1284
+ lines.push('');
1285
+ lines.push(' /* Code Component */');
1286
+ if (ds.components.code.fontSize)
1287
+ lines.push(` --code-font-size: ${ds.components.code.fontSize};`);
1288
+ if (ds.components.code.lineHeight)
1289
+ lines.push(` --code-line-height: ${ds.components.code.lineHeight};`);
1290
+ if (ds.components.code.background)
1291
+ lines.push(` --code-background: ${ds.components.code.background};`);
1292
+ if (ds.components.code.color)
1293
+ lines.push(` --code-color: ${ds.components.code.color};`);
1294
+ if (ds.components.code.borderRadius)
1295
+ lines.push(` --code-border-radius: ${ds.components.code.borderRadius};`);
1296
+ if (ds.components.code.padding)
1297
+ lines.push(` --code-padding: ${ds.components.code.padding};`);
1298
+ if (ds.components.code.blockPadding)
1299
+ lines.push(` --code-block-padding: ${ds.components.code.blockPadding};`);
1300
+ if (ds.components.code.blockBorderRadius)
1301
+ lines.push(` --code-block-border-radius: ${ds.components.code.blockBorderRadius};`);
1302
+ }
1303
+ if (ds.components.heading) {
1304
+ lines.push('');
1305
+ lines.push(' /* Heading Component */');
1306
+ if (ds.components.heading.h1FontSize)
1307
+ lines.push(` --heading-h1-font-size: ${ds.components.heading.h1FontSize};`);
1308
+ if (ds.components.heading.h2FontSize)
1309
+ lines.push(` --heading-h2-font-size: ${ds.components.heading.h2FontSize};`);
1310
+ if (ds.components.heading.h3FontSize)
1311
+ lines.push(` --heading-h3-font-size: ${ds.components.heading.h3FontSize};`);
1312
+ if (ds.components.heading.h4FontSize)
1313
+ lines.push(` --heading-h4-font-size: ${ds.components.heading.h4FontSize};`);
1314
+ if (ds.components.heading.h1FontWeight)
1315
+ lines.push(` --heading-h1-font-weight: ${ds.components.heading.h1FontWeight};`);
1316
+ if (ds.components.heading.h2FontWeight)
1317
+ lines.push(` --heading-h2-font-weight: ${ds.components.heading.h2FontWeight};`);
1318
+ if (ds.components.heading.h3FontWeight)
1319
+ lines.push(` --heading-h3-font-weight: ${ds.components.heading.h3FontWeight};`);
1320
+ if (ds.components.heading.h1LineHeight)
1321
+ lines.push(` --heading-h1-line-height: ${ds.components.heading.h1LineHeight};`);
1322
+ if (ds.components.heading.h2LineHeight)
1323
+ lines.push(` --heading-h2-line-height: ${ds.components.heading.h2LineHeight};`);
1324
+ if (ds.components.heading.h3LineHeight)
1325
+ lines.push(` --heading-h3-line-height: ${ds.components.heading.h3LineHeight};`);
1326
+ if (ds.components.heading.marginTop)
1327
+ lines.push(` --heading-margin-top: ${ds.components.heading.marginTop};`);
1328
+ if (ds.components.heading.marginBottom)
1329
+ lines.push(` --heading-margin-bottom: ${ds.components.heading.marginBottom};`);
1330
+ }
1331
+ }
1332
+ lines.push('}');
1333
+ // Add dark mode overrides if provided
1334
+ if (darkMode && darkMode.colors) {
1335
+ lines.push('');
1336
+ lines.push('/* Dark Mode */');
1337
+ lines.push('@media (prefers-color-scheme: dark) {');
1338
+ lines.push(' :root {');
1339
+ if (darkMode.colors.primary) {
1340
+ lines.push(' /* Brand/Primary Colors */');
1341
+ lines.push(` --color-primary-1: ${darkMode.colors.primary['1']};`);
1342
+ lines.push(` --color-primary-2: ${darkMode.colors.primary['2']};`);
1343
+ lines.push(` --color-primary-3: ${darkMode.colors.primary['3']};`);
1344
+ lines.push(` --color-primary-soft: ${darkMode.colors.primary.soft};`);
1345
+ }
1346
+ if (darkMode.colors.success) {
1347
+ lines.push('');
1348
+ lines.push(' /* Semantic Colors */');
1349
+ lines.push(` --color-success-1: ${darkMode.colors.success['1']};`);
1350
+ lines.push(` --color-success-2: ${darkMode.colors.success['2']};`);
1351
+ lines.push(` --color-success-3: ${darkMode.colors.success['3']};`);
1352
+ }
1353
+ if (darkMode.colors.warning) {
1354
+ lines.push(` --color-warning-1: ${darkMode.colors.warning['1']};`);
1355
+ lines.push(` --color-warning-2: ${darkMode.colors.warning['2']};`);
1356
+ lines.push(` --color-warning-3: ${darkMode.colors.warning['3']};`);
1357
+ }
1358
+ if (darkMode.colors.danger) {
1359
+ lines.push(` --color-danger-1: ${darkMode.colors.danger['1']};`);
1360
+ lines.push(` --color-danger-2: ${darkMode.colors.danger['2']};`);
1361
+ lines.push(` --color-danger-3: ${darkMode.colors.danger['3']};`);
1362
+ }
1363
+ if (darkMode.colors.info) {
1364
+ lines.push(` --color-info-1: ${darkMode.colors.info['1']};`);
1365
+ lines.push(` --color-info-2: ${darkMode.colors.info['2']};`);
1366
+ lines.push(` --color-info-3: ${darkMode.colors.info['3']};`);
1367
+ }
1368
+ if (darkMode.colors.gray) {
1369
+ lines.push('');
1370
+ lines.push(' /* Gray Scale */');
1371
+ lines.push(` --color-gray-1: ${darkMode.colors.gray['1']};`);
1372
+ lines.push(` --color-gray-2: ${darkMode.colors.gray['2']};`);
1373
+ lines.push(` --color-gray-3: ${darkMode.colors.gray['3']};`);
1374
+ lines.push(` --color-gray-soft: ${darkMode.colors.gray.soft};`);
1375
+ }
1376
+ if (darkMode.colors.bg) {
1377
+ lines.push('');
1378
+ lines.push(' /* Background Colors */');
1379
+ lines.push(` --bg-default: ${darkMode.colors.bg.default};`);
1380
+ lines.push(` --bg-alt: ${darkMode.colors.bg.alt};`);
1381
+ lines.push(` --bg-elevated: ${darkMode.colors.bg.elevated};`);
1382
+ lines.push(` --bg-soft: ${darkMode.colors.bg.soft};`);
1383
+ }
1384
+ if (darkMode.colors.text) {
1385
+ lines.push('');
1386
+ lines.push(' /* Text Colors */');
1387
+ lines.push(` --text-1: ${darkMode.colors.text['1']};`);
1388
+ lines.push(` --text-2: ${darkMode.colors.text['2']};`);
1389
+ lines.push(` --text-3: ${darkMode.colors.text['3']};`);
1390
+ }
1391
+ if (darkMode.colors.border) {
1392
+ lines.push('');
1393
+ lines.push(' /* Border Colors */');
1394
+ lines.push(` --border-default: ${darkMode.colors.border.default};`);
1395
+ lines.push(` --border-divider: ${darkMode.colors.border.divider};`);
1396
+ lines.push(` --border-gutter: ${darkMode.colors.border.gutter};`);
1397
+ }
1398
+ lines.push(' }');
1399
+ lines.push('}');
1400
+ }
1401
+ return lines.join('\n');
1402
+ }
1403
+ /**
1404
+ * Generate CSS variables for design system
1405
+ */
1406
+ function generateDesignSystemVariables(designSystemName) {
1407
+ try {
1408
+ // Get the design system (default to vitepress if invalid)
1409
+ const validNames = ['vitepress', 'docusaurus', 'rspress'];
1410
+ const name = validNames.includes(designSystemName) ? designSystemName : 'vitepress';
1411
+ const designSystem = getDesignSystem(name);
1412
+ const darkModeOverride = getDarkModeOverride(name);
1413
+ return designSystemToCSS(designSystem, darkModeOverride);
1414
+ }
1415
+ catch (error) {
1416
+ console.error(`Failed to load design system "${designSystemName}":`, error);
1417
+ // Fallback to vitepress if there's an error
1418
+ const designSystem = getDesignSystem('vitepress');
1419
+ const darkModeOverride = getDarkModeOverride('vitepress');
1420
+ return designSystemToCSS(designSystem, darkModeOverride);
1421
+ }
1422
+ }
1423
+ /**
1424
+ * Escape HTML
1425
+ */
1426
+ function escapeHtml(str) {
1427
+ return str
1428
+ .replace(/&/g, '&amp;')
1429
+ .replace(/</g, '&lt;')
1430
+ .replace(/>/g, '&gt;')
1431
+ .replace(/"/g, '&quot;')
1432
+ .replace(/'/g, '&#039;');
1433
+ }
1434
+ /**
1435
+ * Escape JavaScript strings
1436
+ */
1437
+ function escapeJsString(str) {
1438
+ return str
1439
+ .replace(/\\/g, '\\\\')
1440
+ .replace(/'/g, "\\'")
1441
+ .replace(/"/g, '\\"')
1442
+ .replace(/\n/g, '\\n')
1443
+ .replace(/\r/g, '\\r');
1444
+ }
1445
+ /**
1446
+ * Run @marko/run build
1447
+ */
1448
+ async function runMarkoRunBuild(outDir, debug, root) {
1449
+ return new Promise((resolve) => {
1450
+ const args = ['build'];
1451
+ if (outDir) {
1452
+ args.push('--output', outDir);
1453
+ }
1454
+ if (debug) {
1455
+ args.push('--debug');
1456
+ }
1457
+ const buildProcess = spawn('npx', ['marko-run', ...args], {
1458
+ stdio: 'inherit',
1459
+ cwd: root,
1460
+ });
1461
+ buildProcess.on('close', (code) => {
1462
+ if (code === 0) {
1463
+ // Determine output directory
1464
+ const outputDir = outDir || 'dist';
1465
+ resolve({
1466
+ success: true,
1467
+ outDir: path.join(root, outputDir),
1468
+ errors: [],
1469
+ });
1470
+ }
1471
+ else {
1472
+ resolve({
1473
+ success: false,
1474
+ outDir: '',
1475
+ errors: [`Build process exited with code ${code}`],
1476
+ });
1477
+ }
1478
+ });
1479
+ buildProcess.on('error', (error) => {
1480
+ resolve({
1481
+ success: false,
1482
+ outDir: '',
1483
+ errors: [`Failed to start build process: ${error.message}`],
1484
+ });
1485
+ });
1486
+ });
1487
+ }
1488
+ /**
1489
+ * Generate catch-all routes using dynamic routes
1490
+ * Supports generic modules (not just hardcoded docs/blog/pages)
1491
+ */
1492
+ export async function generateCatchAllRoutes(manifest, routesDir, config, modules, debug) {
1493
+ console.log(' Using catch-all dynamic routes...');
1494
+ console.log(' Content will be rendered at request time');
1495
+ // Generate catch-all routes for each content directory from config
1496
+ const contentDirs = config.content || {};
1497
+ for (const [moduleId, dirConfig] of Object.entries(contentDirs)) {
1498
+ if (!dirConfig)
1499
+ continue;
1500
+ // Skip non-directory entries
1501
+ if (typeof dirConfig === 'object' && dirConfig !== null && !('dir' in dirConfig)) {
1502
+ continue;
1503
+ }
1504
+ if (moduleId === 'pages') {
1505
+ // Generate catch-all route for pages (root-level, no module prefix)
1506
+ const pagesDir = path.join(routesDir, '$$slug');
1507
+ await fs.mkdir(pagesDir, { recursive: true });
1508
+ // Handler - no manifest needed, content rendered at request time
1509
+ const pagesHandler = await loadTemplate('catch-all-handler.js.template', {
1510
+ CONTENT_TYPE: 'pages',
1511
+ CONFIG_PATH: '../_config.js',
1512
+ VITE_PLUGIN_PATH: 'markopress/build',
1513
+ });
1514
+ await fs.writeFile(path.join(pagesDir, '+handler.js'), pagesHandler);
1515
+ // Page
1516
+ const pagesPage = await loadTemplate('catch-all-page.marko.template', {
1517
+ CONTENT_TYPE_CLASS: 'page',
1518
+ });
1519
+ await fs.writeFile(path.join(pagesDir, '+page.marko'), pagesPage);
1520
+ if (debug)
1521
+ console.log(` Generated pages catch-all route`);
1522
+ }
1523
+ else {
1524
+ // Generate catch-all route for non-pages modules (blog, guides, etc.)
1525
+ const moduleDir = path.join(routesDir, moduleId, '$$slug');
1526
+ await fs.mkdir(moduleDir, { recursive: true });
1527
+ // Handler - no manifest needed
1528
+ const handlerTemplate = await loadTemplate('catch-all-handler.js.template', {
1529
+ CONTENT_TYPE: moduleId,
1530
+ CONFIG_PATH: '../../_config.js',
1531
+ VITE_PLUGIN_PATH: 'markopress/build',
1532
+ });
1533
+ await fs.writeFile(path.join(moduleDir, '+handler.js'), handlerTemplate);
1534
+ // Page
1535
+ const pageTemplate = await loadTemplate('catch-all-page.marko.template', {
1536
+ CONTENT_TYPE_CLASS: moduleId,
1537
+ });
1538
+ await fs.writeFile(path.join(moduleDir, '+page.marko'), pageTemplate);
1539
+ if (debug)
1540
+ console.log(` Generated ${moduleId} catch-all route`);
1541
+ }
1542
+ }
1543
+ // Step 3: Generate config file for handlers
1544
+ await generateConfigFile(routesDir, config, debug);
1545
+ // Step 4: Generate vite.config.js for markdown content virtual module support
1546
+ await generateViteConfig(config.root, debug);
1547
+ // Step 5: Generate root layout that wraps all pages
1548
+ await generateRootLayout(routesDir, config, debug);
1549
+ }
1550
+ // Re-export markdown plugin utilities for use in generated handlers
1551
+ export { loadMarkdownModule, registerMarkdownContent };
1552
+ export { markdownContentPlugin } from './vite-markdown-plugin.js';
1553
+ //# sourceMappingURL=index.js.map