@publish.os/nextjs 0.0.1-alpha.90 → 0.0.1-alpha.92

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 (284) hide show
  1. package/compiler-assets/nextjs/defaults/group/index.tsx +16 -11
  2. package/compiler-assets/nextjs/defaults/group/layout.tsx +6 -6
  3. package/compiler-assets/nextjs/defaults/group/page.tsx +36 -26
  4. package/compiler-assets/nextjs/defaults/project/next.config.mjs +21 -11
  5. package/compiler-assets/nextjs/defaults/project/tsconfig.json.template +10 -0
  6. package/compiler-assets/nextjs/defaults/root/css/components/code-block.css +18 -0
  7. package/compiler-assets/nextjs/defaults/root/index.tsx +16 -11
  8. package/compiler-assets/nextjs/defaults/root/layout.tsx +7 -3
  9. package/compiler-assets/nextjs/shims/agent-user-agents.ts +65 -0
  10. package/compiler-assets/nextjs/shims/document.tsx +46 -0
  11. package/compiler-assets/nextjs/shims/generated-leaf-page.tsx +5 -0
  12. package/compiler-assets/nextjs/shims/global-layout.tsx +8 -42
  13. package/compiler-assets/nextjs/shims/global-not-found.tsx +69 -0
  14. package/compiler-assets/nextjs/shims/markdown-route.ts +2 -0
  15. package/compiler-assets/nextjs/shims/metadata-icons.tsx +39 -0
  16. package/compiler-assets/nextjs/shims/not-found-content.tsx +53 -0
  17. package/compiler-assets/nextjs/shims/not-found-navigation.tsx +60 -0
  18. package/compiler-assets/nextjs/shims/proxy.ts +254 -86
  19. package/compiler-assets/nextjs/shims/site-fenced-code-block.tsx +2 -6
  20. package/compiler-assets/nextjs/shims/site-mdx-components.tsx +28 -16
  21. package/compiler-assets/nextjs/shims/site-mdx.tsx +6 -1
  22. package/dist/{build-operation-2JAYBSKF.js → build-operation-CJDUL4OW.js} +29 -23
  23. package/dist/{button-c3WGnxIj.d.ts → button-D6Xp2HBm.d.ts} +4 -4
  24. package/dist/capture-local-source-WGGDIUU2.js +258 -0
  25. package/dist/chunk-22RSSUNV.js +13 -0
  26. package/dist/chunk-3T4HVDSC.js +426 -0
  27. package/dist/{chunk-WKTEO6RS.js → chunk-4BC2L2KA.js} +46 -12
  28. package/dist/{chunk-WDY4IK6Z.js → chunk-5J3YUNRY.js} +3 -10
  29. package/dist/chunk-5KAD7W53.js +248 -0
  30. package/dist/{chunk-DWA3ZRGW.js → chunk-6C65KV7O.js} +2 -2
  31. package/dist/{chunk-GGKFQ3MA.js → chunk-6D2E6UMD.js} +144 -70
  32. package/dist/{chunk-7OGTPSKI.js → chunk-7NY22B5U.js} +8 -3
  33. package/dist/chunk-DX7PXFCM.js +2519 -0
  34. package/dist/{chunk-726GV2NF.js → chunk-DZ3VWEV5.js} +950 -632
  35. package/dist/chunk-EFO6AWXN.js +19 -0
  36. package/dist/chunk-IL4RJOKI.js +186 -0
  37. package/dist/{chunk-H6XILMZX.js → chunk-NHSLTARU.js} +2588 -1493
  38. package/dist/{chunk-DLDFHU7Y.js → chunk-NNBJZPFM.js} +4 -2
  39. package/dist/{chunk-VO2I7MTM.js → chunk-SBMDONTV.js} +10 -7
  40. package/dist/{chunk-YLTOLYWM.js → chunk-WL75GQ5M.js} +6 -18
  41. package/dist/compiler.d.ts +15 -2
  42. package/dist/compiler.js +111 -23
  43. package/dist/components/blocks/agent-actions.d.ts +81 -0
  44. package/dist/components/blocks/agent-actions.js +563 -0
  45. package/dist/components/blocks/toc.d.ts +43 -0
  46. package/dist/components/blocks/toc.js +136 -0
  47. package/dist/components/ui/button.d.ts +4 -0
  48. package/dist/components/ui/button.js +94 -0
  49. package/dist/compose-44GJSMXD.js +16 -0
  50. package/dist/config/createNextConfig.js +19 -3
  51. package/dist/{dev-operation-TZDVJCN7.js → dev-operation-WNECBHZ5.js} +134 -84
  52. package/dist/{framework-selection-X4U6Y6QC.js → framework-selection-ZVO323PK.js} +3 -2
  53. package/dist/ide-support-TG74L4FF.js +16 -0
  54. package/dist/index.d.ts +151 -95
  55. package/dist/index.js +25 -0
  56. package/dist/jsx-dev-runtime.d.ts +6 -0
  57. package/dist/jsx-dev-runtime.js +100 -0
  58. package/dist/jsx-runtime.d.ts +7 -0
  59. package/dist/jsx-runtime.js +97 -0
  60. package/dist/kernel/components/blocks/AgentActions.d.ts +37 -8
  61. package/dist/kernel/components/blocks/AgentActions.js +384 -174
  62. package/dist/kernel/components/blocks/agent-actions.d.ts +4 -0
  63. package/dist/kernel/components/blocks/agent-actions.js +563 -0
  64. package/dist/kernel/components/blocks/animated-icon-swap.d.ts +2 -2
  65. package/dist/kernel/components/blocks/animated-icon-swap.js +1 -1
  66. package/dist/kernel/components/blocks/color-palette.d.ts +2 -2
  67. package/dist/kernel/components/blocks/color-palette.js +4 -4
  68. package/dist/kernel/components/blocks/logo-block.d.ts +2 -2
  69. package/dist/kernel/components/blocks/logo-block.js +1 -1
  70. package/dist/kernel/components/blocks/nav-breadcrumb.d.ts +6 -5
  71. package/dist/kernel/components/blocks/nav-breadcrumb.js +21 -15
  72. package/dist/kernel/components/blocks/navbar.d.ts +5 -4
  73. package/dist/kernel/components/blocks/navbar.js +79 -59
  74. package/dist/kernel/components/blocks/navigation-progress.d.ts +2 -2
  75. package/dist/kernel/components/blocks/navigation-progress.js +2 -2
  76. package/dist/kernel/components/blocks/next-prev.d.ts +4 -4
  77. package/dist/kernel/components/blocks/next-prev.js +30 -7
  78. package/dist/kernel/components/blocks/page-list.d.ts +4 -4
  79. package/dist/kernel/components/blocks/page-list.js +32 -9
  80. package/dist/kernel/components/blocks/search.d.ts +9 -15
  81. package/dist/kernel/components/blocks/search.js +16 -17
  82. package/dist/kernel/components/blocks/sidebar-node.d.ts +3 -5
  83. package/dist/kernel/components/blocks/sidebar-node.js +13 -3
  84. package/dist/kernel/components/blocks/sidebar.d.ts +6 -7
  85. package/dist/kernel/components/blocks/sidebar.js +137 -28
  86. package/dist/kernel/components/blocks/theme-toggle.d.ts +3 -4
  87. package/dist/kernel/components/blocks/theme-toggle.js +5 -5
  88. package/dist/kernel/components/blocks/toc-client.d.ts +40 -0
  89. package/dist/kernel/components/blocks/toc-client.js +495 -0
  90. package/dist/kernel/components/blocks/toc.d.ts +20 -2
  91. package/dist/kernel/components/blocks/toc.js +121 -179
  92. package/dist/kernel/components/mdx/components/blockquote.d.ts +2 -2
  93. package/dist/kernel/components/mdx/components/blockquote.js +1 -1
  94. package/dist/kernel/components/mdx/components/callout.d.ts +3 -3
  95. package/dist/kernel/components/mdx/components/callout.js +1 -1
  96. package/dist/kernel/components/mdx/components/code-block.d.ts +2 -2
  97. package/dist/kernel/components/mdx/components/code-block.js +2 -2
  98. package/dist/kernel/components/mdx/components/copy-code-button.d.ts +2 -2
  99. package/dist/kernel/components/mdx/components/copy-code-button.js +5 -4
  100. package/dist/kernel/components/mdx/components/figure-caption.d.ts +3 -3
  101. package/dist/kernel/components/mdx/components/figure-caption.js +3 -2
  102. package/dist/kernel/components/mdx/components/figure.d.ts +8 -4
  103. package/dist/kernel/components/mdx/components/figure.js +23 -13
  104. package/dist/kernel/components/mdx/components/footnotes.d.ts +2 -2
  105. package/dist/kernel/components/mdx/components/footnotes.js +1 -1
  106. package/dist/kernel/components/mdx/components/heading-copy-link.d.ts +2 -2
  107. package/dist/kernel/components/mdx/components/heading-copy-link.js +1 -1
  108. package/dist/kernel/components/mdx/components/heading.d.ts +2 -2
  109. package/dist/kernel/components/mdx/components/heading.js +6 -3
  110. package/dist/kernel/components/mdx/components/hr.d.ts +4 -4
  111. package/dist/kernel/components/mdx/components/hr.js +2 -2
  112. package/dist/kernel/components/mdx/components/image-zoom.d.ts +3 -3
  113. package/dist/kernel/components/mdx/components/image-zoom.js +4 -3
  114. package/dist/kernel/components/mdx/components/image.d.ts +2 -2
  115. package/dist/kernel/components/mdx/components/image.js +22 -17
  116. package/dist/kernel/components/mdx/components/inline-code.d.ts +2 -2
  117. package/dist/kernel/components/mdx/components/inline-code.js +1 -1
  118. package/dist/kernel/components/mdx/components/kbd.d.ts +3 -3
  119. package/dist/kernel/components/mdx/components/kbd.js +2 -2
  120. package/dist/kernel/components/mdx/components/link.d.ts +6 -6
  121. package/dist/kernel/components/mdx/components/link.js +2 -2
  122. package/dist/kernel/components/mdx/components/paragraph.d.ts +2 -2
  123. package/dist/kernel/components/mdx/components/paragraph.js +1 -1
  124. package/dist/kernel/components/mdx/components/strikethrough.d.ts +2 -2
  125. package/dist/kernel/components/mdx/components/strikethrough.js +1 -1
  126. package/dist/kernel/components/mdx/components/table.d.ts +7 -7
  127. package/dist/kernel/components/mdx/components/table.js +2 -2
  128. package/dist/kernel/components/mdx/components/task-list.d.ts +3 -3
  129. package/dist/kernel/components/mdx/components/task-list.js +1 -1
  130. package/dist/kernel/components/mdx/components/video-player.d.ts +8 -4
  131. package/dist/kernel/components/mdx/components/video-player.js +6 -2
  132. package/dist/kernel/components/mdx/components/video.d.ts +5 -4
  133. package/dist/kernel/components/mdx/components/video.js +7 -5
  134. package/dist/kernel/components/mdx/error-boundary.d.ts +3 -4
  135. package/dist/kernel/components/mdx/error-boundary.js +2 -2
  136. package/dist/kernel/components/mdx/error-fallback.d.ts +2 -2
  137. package/dist/kernel/components/mdx/error-fallback.js +1 -1
  138. package/dist/kernel/components/mdx/mdx.d.ts +57 -16
  139. package/dist/kernel/components/mdx/mdx.js +232 -35
  140. package/dist/kernel/components/mdx/rehype-heading-ids.d.ts +1 -0
  141. package/dist/kernel/components/mdx/rehype-heading-ids.js +5 -3
  142. package/dist/kernel/components/mdx/types.d.ts +1 -1
  143. package/dist/kernel/components/next-image.d.ts +12 -0
  144. package/dist/kernel/components/next-image.js +61 -0
  145. package/dist/kernel/components/ui/accordion.d.ts +7 -7
  146. package/dist/kernel/components/ui/accordion.js +1 -1
  147. package/dist/kernel/components/ui/breadcrumb-trail.d.ts +3 -4
  148. package/dist/kernel/components/ui/breadcrumb-trail.js +2 -2
  149. package/dist/kernel/components/ui/breadcrumb.d.ts +14 -14
  150. package/dist/kernel/components/ui/breadcrumb.js +4 -4
  151. package/dist/kernel/components/ui/button-group.d.ts +6 -6
  152. package/dist/kernel/components/ui/button-group.js +3 -3
  153. package/dist/kernel/components/ui/button.d.ts +59 -7
  154. package/dist/kernel/components/ui/button.js +43 -39
  155. package/dist/kernel/components/ui/card.d.ts +7 -7
  156. package/dist/kernel/components/ui/card.js +1 -1
  157. package/dist/kernel/components/ui/dialog.d.ts +10 -10
  158. package/dist/kernel/components/ui/dialog.js +1 -1
  159. package/dist/kernel/components/ui/drawer.d.ts +12 -12
  160. package/dist/kernel/components/ui/drawer.js +1 -1
  161. package/dist/kernel/components/ui/input.d.ts +2 -2
  162. package/dist/kernel/components/ui/input.js +1 -1
  163. package/dist/kernel/components/ui/kbd.d.ts +4 -4
  164. package/dist/kernel/components/ui/kbd.js +1 -1
  165. package/dist/kernel/components/ui/link.d.ts +11 -5
  166. package/dist/kernel/components/ui/link.js +2 -2
  167. package/dist/kernel/components/ui/menu.d.ts +22 -22
  168. package/dist/kernel/components/ui/menu.js +1 -1
  169. package/dist/kernel/components/ui/mod-key.d.ts +2 -3
  170. package/dist/kernel/components/ui/mod-key.js +2 -2
  171. package/dist/kernel/components/ui/navigation-menu.d.ts +15 -15
  172. package/dist/kernel/components/ui/navigation-menu.js +1 -1
  173. package/dist/kernel/components/ui/popover.d.ts +11 -11
  174. package/dist/kernel/components/ui/popover.js +1 -1
  175. package/dist/kernel/components/ui/progress.d.ts +7 -7
  176. package/dist/kernel/components/ui/progress.js +1 -1
  177. package/dist/kernel/components/ui/scroll-area.d.ts +9 -10
  178. package/dist/kernel/components/ui/scroll-area.js +1 -1
  179. package/dist/kernel/components/ui/select.d.ts +16 -16
  180. package/dist/kernel/components/ui/select.js +1 -1
  181. package/dist/kernel/components/ui/separator.d.ts +3 -3
  182. package/dist/kernel/components/ui/separator.js +1 -1
  183. package/dist/kernel/components/ui/table.d.ts +7 -7
  184. package/dist/kernel/components/ui/table.js +1 -1
  185. package/dist/kernel/components/ui/tabs.d.ts +12 -12
  186. package/dist/kernel/components/ui/tabs.js +1 -1
  187. package/dist/kernel/components/ui/tag.d.ts +3 -3
  188. package/dist/kernel/components/ui/tag.js +1 -1
  189. package/dist/kernel/components/ui/text.d.ts +4 -4
  190. package/dist/kernel/components/ui/text.js +1 -1
  191. package/dist/kernel/components/ui/toast.d.ts +11 -11
  192. package/dist/kernel/components/ui/toast.js +1 -1
  193. package/dist/kernel/components/ui/tooltip.d.ts +9 -9
  194. package/dist/kernel/components/ui/tooltip.js +1 -1
  195. package/dist/kernel/defaults.registry.d.ts +4 -17
  196. package/dist/kernel/defaults.registry.js +4 -19
  197. package/dist/kernel/routes/agent/markdown.d.ts +7 -0
  198. package/dist/kernel/routes/agent/markdown.js +229 -54
  199. package/dist/kernel/routes/layouts/group.d.ts +1 -1
  200. package/dist/kernel/routes/layouts/group.js +5 -7
  201. package/dist/kernel/routes/metadata/feed.d.ts +1 -2
  202. package/dist/kernel/routes/metadata/feed.js +119 -88
  203. package/dist/kernel/routes/metadata/llms.d.ts +3 -23
  204. package/dist/kernel/routes/metadata/llms.js +2 -103
  205. package/dist/kernel/routes/metadata/og-image.js +1 -1
  206. package/dist/kernel/routes/metadata/robots.d.ts +5 -3
  207. package/dist/kernel/routes/metadata/robots.js +4 -25
  208. package/dist/kernel/routes/metadata/sitemap.d.ts +36 -3
  209. package/dist/kernel/routes/metadata/sitemap.js +277 -49
  210. package/dist/kernel/routes/not-found.d.ts +34 -0
  211. package/dist/kernel/routes/not-found.js +213 -0
  212. package/dist/kernel/routes/pages/group.d.ts +5 -5
  213. package/dist/kernel/routes/pages/group.js +11 -5
  214. package/dist/kernel/routes/pages/leaf.d.ts +38 -6
  215. package/dist/kernel/routes/pages/leaf.js +21 -12
  216. package/dist/kernel/routes/pages/root.d.ts +5 -5
  217. package/dist/kernel/routes/pages/root.js +21 -6
  218. package/dist/keyboard-shortcuts.d.ts +2 -2
  219. package/dist/keyboard-shortcuts.js +1 -1
  220. package/dist/manifest-types-CcVPH_jh.d.ts +83 -0
  221. package/dist/resolvers/manifest-resolver.d.ts +2 -2
  222. package/dist/resolvers/manifest-resolver.js +65 -35
  223. package/dist/route-metadata-og-image-BeKKFHg7.d.ts +58 -0
  224. package/dist/search.d.ts +2 -9
  225. package/dist/search.js +9 -16
  226. package/dist/sidebar-types-CMrMM0Nj.d.ts +105 -0
  227. package/dist/theme-scope.d.ts +2 -2
  228. package/dist/theme-scope.js +1 -1
  229. package/dist/types-BbqmOzLC.d.ts +168 -0
  230. package/dist/types-TjH1ek0X.d.ts +22 -0
  231. package/dist/types-sM2g9nW0.d.ts +12 -0
  232. package/dist/types.d.ts +117 -91
  233. package/dist/utils/heading-ids.js +30 -11
  234. package/dist/utils/toc-selection.d.ts +7 -0
  235. package/dist/utils/toc-selection.js +14 -0
  236. package/dist/validate-local-source-EN2GW4FR.js +16 -0
  237. package/dist/validate-publish-os-source-OEWQDB7B.js +14 -0
  238. package/init/seed/minimal/publish.os/assets/blog/hello-world.svg +13 -0
  239. package/init/seed/minimal/publish.os/assets/favicon.dark.svg +13 -0
  240. package/init/seed/minimal/publish.os/assets/favicon.svg +13 -0
  241. package/init/seed/minimal/publish.os/blog/content/hello-world.mdx +48 -0
  242. package/init/seed/minimal/publish.os/blog/content/series-a.mdx +11 -0
  243. package/init/seed/minimal/publish.os/blog/index.tsx +85 -0
  244. package/init/seed/minimal/publish.os/blog/layout.tsx +13 -0
  245. package/init/seed/minimal/publish.os/blog/page.tsx +91 -0
  246. package/init/seed/minimal/publish.os/components/blog/copy-link.tsx +64 -0
  247. package/init/seed/minimal/publish.os/components/blog/frontmatter.ts +41 -0
  248. package/init/seed/minimal/publish.os/docs/content/get-started.mdx +2 -3
  249. package/init/seed/minimal/publish.os/docs/content/groups.mdx +5 -6
  250. package/init/seed/minimal/publish.os/publish.os.config.json +17 -2
  251. package/init/seed/minimal/publish.os/theme.css +3 -3
  252. package/init/seed/minimal/seed.json +12 -4
  253. package/init/seed/scratch/publish.os/docs/content/get-started.mdx +1 -1
  254. package/init/seed/scratch/publish.os/publish.os.config.json +1 -1
  255. package/package.json +48 -23
  256. package/release/app/phosphor/package.json +4 -4
  257. package/release/app/phosphor/pnpm-lock.yaml.template +123 -184
  258. package/release/compiler/package.json +5 -3
  259. package/release/compiler/pnpm-lock.yaml.template +153 -183
  260. package/release/release.json +6 -6
  261. package/dist/button-CtzUJB8u.d.ts +0 -11
  262. package/dist/chunk-535TM4A5.js +0 -1455
  263. package/dist/chunk-B2V22WRT.js +0 -297
  264. package/dist/chunk-NECL5IBJ.js +0 -318
  265. package/dist/chunk-SVYGXXYR.js +0 -36
  266. package/dist/chunk-UF6ATARM.js +0 -1100
  267. package/dist/compose-NN23UJGC.js +0 -13
  268. package/dist/ide-support-ZNW7ODFY.js +0 -15
  269. package/dist/kernel/components/blocks/design-system-preview.d.ts +0 -61
  270. package/dist/kernel/components/blocks/design-system-preview.js +0 -667
  271. package/dist/kernel/components/blocks/link-playground.d.ts +0 -5
  272. package/dist/kernel/components/blocks/link-playground.js +0 -744
  273. package/dist/kernel/components/blocks/navigation-progress-playground.d.ts +0 -5
  274. package/dist/kernel/components/blocks/navigation-progress-playground.js +0 -629
  275. package/dist/kernel/components/ui/theme-toggle.d.ts +0 -24
  276. package/dist/kernel/components/ui/theme-toggle.js +0 -53
  277. package/dist/sidebar-types-DKpR27II.d.ts +0 -25
  278. package/dist/types-BzkZty3r.d.ts +0 -147
  279. package/dist/types-CJWRG05y.d.ts +0 -76
  280. package/dist/types-CUtrppxt.d.ts +0 -69
  281. package/dist/validate-local-source-XRL2Y7XE.js +0 -14
  282. package/dist/validate-publish-os-source-DAPZ4QGE.js +0 -12
  283. package/init/seed/minimal/publish.os/assets/favicon/favicon.dark.png +0 -0
  284. package/init/seed/minimal/publish.os/assets/favicon/favicon.png +0 -0
@@ -1,45 +1,64 @@
1
1
  import {
2
- supportedNextjsSourceImports
3
- } from "./chunk-NECL5IBJ.js";
2
+ AppError as AppError2,
3
+ ERROR_CODES,
4
+ assertPublishOsSourceBoundary
5
+ } from "./chunk-7NY22B5U.js";
4
6
  import {
7
+ FrontmatterParseError,
8
+ MdxIconImportError,
5
9
  ROOT_CONTENT_GROUP_KEY,
6
10
  ROOT_CONTENT_GROUP_TITLE,
7
11
  assertSourceDependency,
12
+ authoredDate,
8
13
  buildFaviconsAndLogos,
9
14
  buildMediaAssets,
10
15
  collectBindingNames,
16
+ convertMdxToMarkdown,
11
17
  groupedMdxExports,
12
18
  joinUrlPath,
13
- localCssImports,
14
19
  mdxComponentExportName,
15
20
  namedValueExports,
16
21
  normalizeSlugPath,
17
22
  normalizeUrlPath,
18
- replacedMdxSourceFiles
19
- } from "./chunk-535TM4A5.js";
23
+ originalDefaultComponentSourcePath,
24
+ parseSourceFrontmatter,
25
+ replacedMdxSourceFiles,
26
+ supportedNextjsSourceImports,
27
+ validateMdxIconImports
28
+ } from "./chunk-DX7PXFCM.js";
20
29
  import {
21
- AppError as AppError2,
22
- ERROR_CODES,
23
- assertPublishOsSourceBoundary
24
- } from "./chunk-7OGTPSKI.js";
30
+ isFrameworkNamespacePath
31
+ } from "./chunk-EFO6AWXN.js";
32
+ import {
33
+ localCssImports
34
+ } from "./chunk-5KAD7W53.js";
25
35
  import {
26
36
  isApprovedPublishOsPath,
27
37
  isExcludedSourcePath,
28
- isFrameworkNamespacePath,
29
38
  isReservedFrameworkPath,
30
39
  isSameOrDescendant
31
- } from "./chunk-YLTOLYWM.js";
40
+ } from "./chunk-WL75GQ5M.js";
32
41
  import {
33
42
  runtimePackageVersion
34
43
  } from "./chunk-AI2UULNX.js";
35
44
  import {
36
- AppError,
37
45
  readInstalledFrameworkLocation
38
- } from "./chunk-WDY4IK6Z.js";
46
+ } from "./chunk-5J3YUNRY.js";
47
+ import {
48
+ AppError
49
+ } from "./chunk-22RSSUNV.js";
39
50
 
40
51
  // src/lib/compiler/validation/validate-publish-os-source.ts
41
52
  import { readdir as readdir3, readFile as readFile5, stat as stat3 } from "fs/promises";
42
- import { basename, dirname, extname as extname3, relative as relative4, resolve as resolve7, sep } from "path";
53
+ import {
54
+ basename as basename2,
55
+ dirname,
56
+ extname as extname4,
57
+ parse,
58
+ relative as relative4,
59
+ resolve as resolve7,
60
+ sep
61
+ } from "path";
43
62
  import ts2 from "typescript";
44
63
 
45
64
  // src/lib/compiler/source/diagnostics/diagnostic-store.ts
@@ -48,7 +67,7 @@ var diagnosticSchema = z.object({
48
67
  code: z.string(),
49
68
  level: z.union([z.literal("error"), z.literal("warning")]),
50
69
  message: z.string(),
51
- details: z.record(z.unknown()).optional()
70
+ details: z.record(z.string(), z.unknown()).optional()
52
71
  }), DiagnosticStore = class {
53
72
  diagnostics = [];
54
73
  add(diagnostic) {
@@ -86,11 +105,22 @@ import { resolve } from "path";
86
105
 
87
106
  // src/lib/compiler/source/config/format-config-issues.ts
88
107
  function issueMessages(issue) {
89
- return issue.code === "invalid_union" ? issue.unionErrors.flatMap(
90
- (error) => error.issues.flatMap(issueMessages)
91
- ) : issue.code === "unrecognized_keys" ? issue.keys.map(
92
- (key) => `${[...issue.path, key].join(".")}: Unknown field.`
93
- ) : [`${issue.path.join(".") || "(root)"}: ${issue.message}`];
108
+ return issue.code === "invalid_union" ? issue.errors.flatMap(
109
+ (issues) => issues.flatMap(
110
+ (nestedIssue) => issueMessages({
111
+ ...nestedIssue,
112
+ path: [...issue.path, ...nestedIssue.path]
113
+ })
114
+ )
115
+ ) : issue.code === "unrecognized_keys" ? issue.keys.map((key) => {
116
+ let path = [...issue.path, key].join(".");
117
+ return issue.path.join(".") === "media.assets" && [
118
+ "maxImageBytes",
119
+ "maxVideoBytes",
120
+ "maxTotalBytes",
121
+ "maxFileCount"
122
+ ].includes(key) ? `${path}: Media limits are fixed by PublishOS. Remove this field.` : `${path}: Unknown field.`;
123
+ }) : [`${issue.path.join(".") || "(root)"}: ${issue.message}`];
94
124
  }
95
125
  function formatConfigIssues(error) {
96
126
  return [...new Set(error.issues.flatMap(issueMessages))].join(`
@@ -100,68 +130,115 @@ function formatConfigIssues(error) {
100
130
  // src/lib/compiler/source/config/publish-os-config-schema.ts
101
131
  import { z as z4 } from "zod";
102
132
 
103
- // src/lib/compiler/source/config/og-metadata-schema.ts
133
+ // src/lib/compiler/source/config/metadata-schema.ts
104
134
  import { z as z2 } from "zod";
105
- var ogImageSchema = z2.union([
106
- z2.string().min(1),
107
- z2.object({ src: z2.string().min(1), alt: z2.string().optional() }).strict()
108
- ]), ogIndexMetadataSchema = z2.object({
135
+ var imagePathSchema = z2.string().min(1).refine((value) => {
136
+ if (value.startsWith("/assets/") && value.length > 8)
137
+ return !0;
138
+ try {
139
+ return new URL(value).protocol === "https:";
140
+ } catch {
141
+ return !1;
142
+ }
143
+ }, "Use a nonempty /assets/ path or an absolute HTTPS URL."), imageSchema = z2.union([
144
+ imagePathSchema,
145
+ z2.object({ src: imagePathSchema, alt: z2.string().optional() }).strict()
146
+ ]), socialFields = {
109
147
  title: z2.string().optional(),
110
148
  description: z2.string().optional(),
111
- image: ogImageSchema.optional(),
112
- scope: z2.enum(["page", "subtree"]).optional()
113
- }).strict().refine((value) => value.scope === void 0 || value.image !== void 0, {
114
- message: "Set og.image when you set og.scope.",
115
- path: ["scope"]
116
- });
149
+ image: imageSchema.optional()
150
+ }, pageMetadataSchema = z2.object({
151
+ openGraph: z2.object(socialFields).strict().optional(),
152
+ robots: z2.object({ index: z2.boolean().optional(), follow: z2.boolean().optional() }).strict().optional()
153
+ }).strict(), indexMetadataSchema = z2.object({
154
+ openGraph: z2.object({
155
+ ...socialFields,
156
+ scope: z2.enum(["page", "subtree"]).optional()
157
+ }).strict().refine(
158
+ (value) => value.scope === void 0 || value.image !== void 0,
159
+ {
160
+ message: "Set metadata.openGraph.image when setting scope.",
161
+ path: ["scope"]
162
+ }
163
+ ).optional()
164
+ }).strict();
117
165
 
118
166
  // src/lib/compiler/source/config/publish-os-group-schema.ts
119
167
  import { z as z3 } from "zod";
120
- var treeNodeSchema = z3.lazy(
121
- () => z3.union([
122
- z3.object({
123
- type: z3.literal("section"),
124
- id: z3.string(),
125
- title: z3.string(),
126
- children: z3.array(treeNodeSchema)
127
- }),
168
+ var navigationSortSchema = z3.object({
169
+ source: z3.literal("frontmatter"),
170
+ field: z3.string().min(1),
171
+ type: z3.enum(["date", "number"]),
172
+ direction: z3.enum(["asc", "desc"]).default("asc")
173
+ }).strict(), navigationEntrySchema = z3.lazy(
174
+ () => z3.discriminatedUnion("type", [
128
175
  z3.object({
129
176
  type: z3.literal("page"),
130
- file: z3.string(),
131
- title: z3.string().optional(),
132
- slug: z3.string().optional()
133
- }),
177
+ file: z3.string().min(1).optional(),
178
+ group: z3.string().min(1).optional(),
179
+ label: z3.string().min(1).optional(),
180
+ items: z3.array(navigationEntrySchema).optional(),
181
+ navigation: z3.lazy(() => navigationConfigSchema).optional()
182
+ }).strict(),
183
+ z3.object({
184
+ type: z3.literal("folder"),
185
+ group: z3.string().min(1).optional(),
186
+ label: z3.string().min(1),
187
+ items: z3.array(navigationEntrySchema).optional(),
188
+ navigation: z3.lazy(() => navigationConfigSchema).optional()
189
+ }).strict(),
190
+ z3.object({
191
+ type: z3.literal("section"),
192
+ label: z3.string().min(1),
193
+ items: z3.array(navigationEntrySchema)
194
+ }).strict(),
134
195
  z3.object({
135
196
  type: z3.literal("link"),
136
- id: z3.string(),
137
- title: z3.string(),
138
- url: z3.string()
139
- }),
197
+ label: z3.string().min(1),
198
+ url: z3.string().min(1)
199
+ }).strict(),
140
200
  z3.object({
141
- type: z3.literal("folder"),
142
- id: z3.string(),
143
- title: z3.string(),
144
- group: z3.string().optional(),
145
- children: z3.array(treeNodeSchema).optional()
146
- })
147
- ])
148
- ), groupListSchema = z3.object({
149
- source: z3.enum(["content", "ids"]),
150
- ids: z3.array(z3.string()).optional(),
151
- sort: z3.object({
152
- by: z3.string(),
153
- direction: z3.enum(["asc", "desc"]).optional()
154
- }).optional(),
155
- prevNext: z3.boolean().optional()
156
- }).strict(), outputScopeSchema = z3.enum(["site", "group", "self"]), outputStatusSchema = z3.enum(["published", "draft", "archived"]), outputDeclarationSchema = z3.object({
201
+ type: z3.literal("view"),
202
+ id: z3.string().min(1).optional(),
203
+ label: z3.string().min(1).optional(),
204
+ items: z3.array(navigationEntrySchema)
205
+ }).strict(),
206
+ z3.object({
207
+ type: z3.literal("switch-view"),
208
+ label: z3.string().min(1),
209
+ target: z3.union([
210
+ z3.string().min(1),
211
+ z3.object({ view: z3.string().min(1) }).strict()
212
+ ])
213
+ }).strict()
214
+ ]).superRefine((entry, context) => {
215
+ entry.type === "page" && entry.file && entry.group && context.addIssue({
216
+ code: "custom",
217
+ path: ["group"],
218
+ message: 'A page navigation entry cannot set both "file" and "group". Use "file" for a page or "group" for a group placement.'
219
+ });
220
+ })
221
+ ), navigationConfigSchema = z3.object({
222
+ items: z3.array(navigationEntrySchema),
223
+ sort: z3.unknown().optional()
224
+ }).strict().transform(({ items, sort }) => {
225
+ if (sort === void 0) return { items };
226
+ let parsedSort = navigationSortSchema.safeParse(sort);
227
+ return parsedSort.success ? { items, sort: parsedSort.data } : {
228
+ items,
229
+ sortWarning: 'Invalid navigation sort config. Use source "frontmatter", a field, type "date" or "number", and direction "asc" or "desc". Filename order is used.'
230
+ };
231
+ }), outputScopeSchema = z3.enum(["site", "group", "self"]), outputDeclarationSchema = z3.object({
157
232
  path: z3.string().min(1),
158
233
  scope: outputScopeSchema.optional(),
159
234
  description: z3.string().optional(),
160
235
  limit: z3.number().int().positive().optional()
161
- }).strict(), outputDefaultsDeclarationSchema = z3.object({
236
+ }).strict(), sitemapOutputSchema = outputDeclarationSchema.extend({
237
+ path: z3.string().min(1).optional(),
238
+ mode: z3.enum(["index", "urls"]).optional()
239
+ }), outputDefaultsDeclarationSchema = z3.object({
162
240
  scope: outputScopeSchema.optional(),
163
241
  limit: z3.number().int().positive().optional(),
164
- status: z3.array(outputStatusSchema).optional(),
165
242
  includeDescriptions: z3.boolean().optional()
166
243
  }).strict(), feedsOutputSchema = z3.object({
167
244
  rss: outputDeclarationSchema.optional(),
@@ -172,10 +249,12 @@ var treeNodeSchema = z3.lazy(
172
249
  atom: outputDefaultsDeclarationSchema.optional(),
173
250
  json: outputDefaultsDeclarationSchema.optional()
174
251
  }).strict(), outputsSchema = z3.object({
175
- llms: outputDeclarationSchema.optional(),
252
+ llms: outputDeclarationSchema.or(z3.literal(!1)).optional(),
253
+ llmsFull: outputDeclarationSchema.or(z3.literal(!1)).optional(),
176
254
  feeds: feedsOutputSchema.optional(),
177
- robots: outputDeclarationSchema.optional(),
178
- sitemap: outputDeclarationSchema.optional()
255
+ robots: outputDeclarationSchema.or(z3.literal(!1)).optional(),
256
+ sitemap: sitemapOutputSchema.or(z3.literal(!1)).optional(),
257
+ sitemapMarkdown: outputDeclarationSchema.or(z3.literal(!1)).optional()
179
258
  }).strict(), outputDefaultsSchema = z3.object({
180
259
  llms: outputDefaultsDeclarationSchema.optional(),
181
260
  feeds: feedsOutputDefaultsSchema.optional()
@@ -237,66 +316,35 @@ var httpsOriginSchema = z3.string().transform((value) => normalizeHttpsOrigin(va
237
316
  ]), fontConfigSchema = z3.record(
238
317
  z3.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
239
318
  fontRoleSchema
240
- ), groupConfigDocumentSchema = z3.lazy(
241
- () => z3.object({
242
- origin: httpsOriginSchema.optional(),
243
- og: ogIndexMetadataSchema.optional(),
244
- title: z3.string().optional(),
245
- slug: z3.string().optional(),
246
- path: z3.string().regex(/^\//).optional(),
247
- mode: z3.enum(["tree", "list"]).optional(),
248
- theme: groupThemeSchema.optional(),
249
- fonts: fontConfigSchema.optional(),
250
- meta: z3.record(z3.unknown()).optional(),
251
- tree: z3.array(treeNodeSchema).optional(),
252
- list: groupListSchema.optional(),
253
- outputs: outputsSchema.optional(),
254
- outputDefaults: outputDefaultsSchema.optional()
255
- }).strict()
256
- );
319
+ ), groupConfigDocumentSchema = z3.object({
320
+ origin: httpsOriginSchema.optional(),
321
+ metadata: indexMetadataSchema.optional(),
322
+ title: z3.string().optional(),
323
+ slug: z3.string().optional(),
324
+ path: z3.string().regex(/^\//).optional(),
325
+ theme: groupThemeSchema.optional(),
326
+ fonts: fontConfigSchema.optional(),
327
+ meta: z3.record(z3.string(), z3.unknown()).optional(),
328
+ navigation: navigationConfigSchema.optional(),
329
+ outputs: outputsSchema.optional(),
330
+ outputDefaults: outputDefaultsSchema.optional()
331
+ }).strict();
257
332
 
258
333
  // src/lib/compiler/source/config/publish-os-config-schema.ts
259
334
  var siteSchema = z4.object({
260
335
  name: z4.string().optional(),
261
336
  description: z4.string().optional()
262
- }).strict(), runtimePackageMessage = 'Set runtime to "@publish.os/nextjs@<exact-version>". Replace <exact-version> with the exact framework version for this source.', runtimeSchema = z4.string({
263
- required_error: runtimePackageMessage,
264
- invalid_type_error: runtimePackageMessage
265
- }).refine((value) => runtimePackageVersion(value) !== void 0, {
337
+ }).strict(), runtimePackageMessage = 'Set runtime to "@publish.os/nextjs@<exact-version>". Replace <exact-version> with the exact framework version for this source.', runtimeSchema = z4.string({ error: runtimePackageMessage }).refine((value) => runtimePackageVersion(value) !== void 0, {
266
338
  message: runtimePackageMessage
267
339
  }), rootThemeSchema = z4.object({
268
340
  defaultMode: z4.enum(["system", "light", "dark"]).optional(),
269
341
  forcedMode: z4.enum(["light", "dark"]).optional()
270
- }).strict(), remoteMediaSourceSchema = z4.object({
271
- protocol: z4.literal("https"),
272
- hostname: z4.string().min(1),
273
- pathname: z4.string().optional()
274
- }).strict(), remoteEmbedSourceSchema = z4.object({
275
- provider: z4.enum([
276
- "youtube",
277
- "vimeo",
278
- "loom",
279
- "wistia",
280
- "cloudflare-stream"
281
- ]),
282
- hostname: z4.string().min(1)
283
342
  }).strict(), mediaConfigSchema = z4.object({
284
- assets: z4.object({
285
- maxImageBytes: z4.number().int().positive().optional(),
286
- maxVideoBytes: z4.number().int().positive().optional(),
287
- maxTotalBytes: z4.number().int().positive().optional(),
288
- maxFileCount: z4.number().int().positive().optional()
289
- }).strict().optional(),
290
- remote: z4.object({
291
- images: z4.array(remoteMediaSourceSchema).optional(),
292
- videos: z4.array(remoteMediaSourceSchema).optional(),
293
- embeds: z4.array(remoteEmbedSourceSchema).optional()
294
- }).strict().optional()
343
+ assets: z4.object({}).strict().optional()
295
344
  }).strict(), agentConfigSchema = z4.object({
296
345
  enabled: z4.boolean().optional(),
297
346
  markdownRoutes: z4.boolean().optional(),
298
347
  markdownAccept: z4.boolean().optional(),
299
- plainTextAccept: z4.boolean().optional(),
300
348
  agentDetection: z4.boolean().optional(),
301
349
  discoveryHeaders: z4.boolean().optional(),
302
350
  blockInternalMarkdownRoute: z4.boolean().optional()
@@ -310,11 +358,11 @@ var siteSchema = z4.object({
310
358
  theme: rootThemeSchema.optional(),
311
359
  fonts: fontConfigSchema.optional(),
312
360
  media: mediaConfigSchema.optional(),
313
- og: ogIndexMetadataSchema.optional(),
314
- meta: z4.record(z4.unknown()).optional(),
361
+ metadata: indexMetadataSchema.optional(),
362
+ meta: z4.record(z4.string(), z4.unknown()).optional(),
315
363
  outputs: outputsSchema.optional(),
316
364
  outputDefaults: outputDefaultsSchema.optional(),
317
- groups: z4.record(groupConfigDocumentSchema).default({})
365
+ groups: z4.record(z4.string(), groupConfigDocumentSchema).default({})
318
366
  }).strict();
319
367
 
320
368
  // src/lib/compiler/source/diagnostics/json-source-location.ts
@@ -603,9 +651,17 @@ async function discoverGroups(options) {
603
651
  parent: options.parentKey
604
652
  }
605
653
  });
606
- if (siblingKeys.set(normalized.localKey, entry.name), !await isDiscoverableGroupFolder(folderPath, options.absoluteBase))
607
- continue;
608
- let key = options.parentKey ? `${options.parentKey}/${normalized.localKey}` : normalized.localKey, group = {
654
+ siblingKeys.set(normalized.localKey, entry.name);
655
+ let key = options.parentKey ? `${options.parentKey}/${normalized.localKey}` : normalized.localKey, isDiscoverable = await isDiscoverableGroupFolder(
656
+ folderPath,
657
+ options.absoluteBase
658
+ ), children = await discoverGroups({
659
+ absoluteBase: options.absoluteBase,
660
+ folderPath,
661
+ parentKey: key
662
+ });
663
+ if (!isDiscoverable && children.length === 0) continue;
664
+ let group = {
609
665
  key,
610
666
  localKey: normalized.localKey,
611
667
  localFolderName: entry.name,
@@ -614,13 +670,7 @@ async function discoverGroups(options) {
614
670
  layoutBoundary: normalized.layoutBoundary,
615
671
  hasContent: await pathIsDirectory(join(folderPath, "content"))
616
672
  };
617
- groups.push(group), groups.push(
618
- ...await discoverGroups({
619
- absoluteBase: options.absoluteBase,
620
- folderPath,
621
- parentKey: key
622
- })
623
- );
673
+ groups.push(group, ...children);
624
674
  }
625
675
  return groups;
626
676
  }
@@ -720,7 +770,20 @@ async function collectGroup(options) {
720
770
  localConfigPath,
721
771
  key,
722
772
  absoluteBase
723
- ), merged = mergeGroupConfig(entry, localConfig), slug = normalizeGroupSlug(merged.slug ?? localKey, key), path = merged.path ? normalizeGroupPath(merged.path, key) : joinUrlPath(parentPath, slug), contentPath = join(folderPath, "content");
773
+ ), merged = mergeGroupConfig(entry, localConfig), slug = normalizeGroupSlug(merged.slug ?? localKey, key), path = merged.path ? normalizeGroupPath(merged.path, key) : joinUrlPath(parentPath, slug);
774
+ if (parentKey && (parentPath === "/" ? path === "/" : path === parentPath || !path.startsWith(`${parentPath}/`)))
775
+ throw new AppError2({
776
+ code: ERROR_CODES.CONFIG_GROUP_INVALID_SCHEMA,
777
+ message: `Nested group ${key} must use a path beneath its parent ${parentKey} (${parentPath}); received ${path}`,
778
+ details: {
779
+ group: key,
780
+ parentGroup: parentKey,
781
+ path,
782
+ parentPath,
783
+ correction: `Set the path for ${key} to a path beneath ${parentPath}, or move the group to a top-level source folder.`
784
+ }
785
+ });
786
+ let contentPath = join(folderPath, "content");
724
787
  discovered.hasContent && await ensureDirectoryExists(contentPath, key, "content folder");
725
788
  let existingPathOwner = paths.get(path);
726
789
  if (existingPathOwner && !(path === "/" && existingPathOwner === ROOT_CONTENT_GROUP_KEY))
@@ -731,6 +794,7 @@ async function collectGroup(options) {
731
794
  });
732
795
  paths.set(path, key), groups[key] = {
733
796
  ...merged,
797
+ ...localConfig?.navigation ? { localNavigation: localConfig.navigation } : {},
734
798
  key,
735
799
  slug,
736
800
  path,
@@ -801,25 +865,55 @@ async function resolvePublishOsConfig(baseDir) {
801
865
 
802
866
  // src/lib/compiler/source/content/scan-publish-os-content.ts
803
867
  import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
804
- import { extname, join as join2, relative as relative2 } from "path";
805
- import matter from "gray-matter";
806
- var RESERVED_FIELDS = /* @__PURE__ */ new Set([
807
- "title",
808
- "slug",
809
- "description",
810
- "og",
811
- "status",
812
- "publishedAt",
813
- "updatedAt",
814
- "authors",
815
- "tags"
816
- ]), UNSUPPORTED_FIELDS = /* @__PURE__ */ new Set(["author", "meta", "metadata"]);
868
+ import { extname as extname2, join as join2, relative as relative2 } from "path";
869
+
870
+ // src/lib/compiler/source/content/page-defaults.ts
871
+ import { basename, extname } from "path";
872
+ import remarkMdx from "remark-mdx";
873
+ import remarkParse from "remark-parse";
874
+ import { unified } from "unified";
875
+ var parser = unified().use(remarkParse).use(remarkMdx);
876
+ function defaultTitle(path) {
877
+ return basename(path, extname(path)).replace(/[-_]+/g, " ").replace(
878
+ /(^|\s)(\p{L})/gu,
879
+ (_, space, letter) => space + letter.toLocaleUpperCase()
880
+ );
881
+ }
882
+ function staticText(node) {
883
+ return node.type === "text" ? node.value ?? "" : ["emphasis", "strong", "delete", "link", "linkReference"].includes(
884
+ node.type
885
+ ) ? (node.children ?? []).map((child) => staticText(child)).join("") : "";
886
+ }
887
+ function firstProse(node) {
888
+ if (node.type === "paragraph")
889
+ return (node.children ?? []).map((child) => staticText(child)).join("").replace(/\s+/g, " ").trim() || void 0;
890
+ if (["root", "blockquote", "list", "listItem"].includes(node.type))
891
+ for (let child of node.children ?? []) {
892
+ let prose = firstProse(child);
893
+ if (prose) return prose;
894
+ }
895
+ }
896
+ function defaultDescription(body) {
897
+ let prose = firstProse(parser.parse(body));
898
+ if (!prose) return "";
899
+ let sentence = [
900
+ ...new Intl.Segmenter(void 0, { granularity: "sentence" }).segment(
901
+ prose
902
+ )
903
+ ][0]?.segment.trim() ?? prose, chars = [
904
+ ...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(
905
+ sentence
906
+ )
907
+ ].map(({ segment }) => segment);
908
+ if (chars.length <= 160) return sentence;
909
+ let prefix = chars.slice(0, 159).join(""), boundary = /\s/u.test(chars[159] ?? "") ? -1 : prefix.search(/\s+\S*$/u);
910
+ return `${(boundary > 0 ? prefix.slice(0, boundary) : prefix).trimEnd()}\u2026`;
911
+ }
912
+
913
+ // src/lib/compiler/source/content/scan-publish-os-content.ts
817
914
  function fileSlug(relativePath) {
818
915
  return relativePath.replace(/\\/g, "/").replace(/\.mdx$/i, "");
819
916
  }
820
- function asString(value) {
821
- return typeof value == "string" && value.length > 0 ? value : void 0;
822
- }
823
917
  function normalizeContentSlug(value, absolutePath) {
824
918
  let result = normalizeSlugPath(value);
825
919
  if (result.errors.length > 0)
@@ -834,283 +928,153 @@ function normalizeContentSlug(value, absolutePath) {
834
928
  });
835
929
  return result.value;
836
930
  }
837
- function asDateString(value) {
838
- if (typeof value == "string" && value.length > 0) return value;
839
- if (value instanceof Date && !Number.isNaN(value.getTime()))
840
- return value.toISOString().slice(0, 10);
841
- }
842
- function isIsoDate(value) {
843
- return !Number.isNaN(Date.parse(value));
844
- }
845
931
  function warning(code, message, details) {
846
932
  return { code, level: "warning", message, details };
847
933
  }
848
- function treeSlugs(nodes) {
849
- function visit(tree) {
850
- let slugs = /* @__PURE__ */ new Map();
851
- for (let node of tree)
852
- if (node.type === "page" && (slugs.has(node.file) || slugs.set(node.file, asString(node.slug))), "children" in node && node.children)
853
- for (let [fileID, slug] of visit(node.children))
854
- slug && !slugs.has(fileID) && slugs.set(fileID, slug);
855
- return slugs;
856
- }
857
- return visit(nodes ?? []);
858
- }
859
- function normalizeAuthorship(authors, absolutePath) {
860
- let warnings = [];
861
- if (authors === void 0)
862
- return { authors: [], warnings };
863
- if (!Array.isArray(authors))
864
- return warnings.push(
865
- warning(
866
- "CONTENT_AUTHORS_INVALID",
867
- `Ignoring invalid authors for ${absolutePath}`,
868
- {
869
- path: absolutePath
870
- }
871
- )
872
- ), { authors: [], warnings };
873
- let normalized = [];
874
- for (let author of authors) {
875
- if (typeof author == "string" && author.length > 0) {
876
- normalized.push({ name: author });
877
- continue;
878
- }
879
- if (author && typeof author == "object" && typeof author.name == "string") {
880
- let value = author;
881
- normalized.push({
882
- id: asString(value.id),
883
- name: value.name,
884
- handle: asString(value.handle),
885
- avatar: asString(value.avatar),
886
- url: asString(value.url)
887
- });
888
- continue;
889
- }
890
- warnings.push(
891
- warning(
892
- "CONTENT_AUTHOR_INVALID",
893
- `Ignoring invalid author entry for ${absolutePath}`,
894
- {
895
- path: absolutePath
896
- }
897
- )
898
- );
899
- }
900
- return { authors: normalized, warnings };
901
- }
902
- function normalizeTags(tags, absolutePath) {
903
- return tags === void 0 ? { tags: [], warnings: [] } : Array.isArray(tags) ? {
904
- tags: tags.filter((tag) => typeof tag == "string"),
905
- warnings: []
906
- } : {
907
- tags: [],
908
- warnings: [
909
- warning(
910
- "CONTENT_TAGS_INVALID",
911
- `Ignoring invalid tags for ${absolutePath}`,
912
- {
913
- path: absolutePath
914
- }
915
- )
916
- ]
917
- };
918
- }
919
- function normalizeOgMetadata(value, absolutePath) {
920
- if (value === void 0) return;
921
- if (!value || typeof value != "object" || Array.isArray(value))
922
- throw new AppError2({
923
- code: "CONTENT_FRONTMATTER_OG_INVALID",
924
- message: `Invalid og frontmatter for ${absolutePath}; expected an object with title, description, or image`,
925
- details: { path: absolutePath, field: "og" }
926
- });
927
- let source = value, allowedFields = /* @__PURE__ */ new Set(["title", "description", "image"]);
928
- for (let field of Object.keys(source))
929
- if (!allowedFields.has(field))
930
- throw new AppError2({
931
- code: "CONTENT_FRONTMATTER_OG_INVALID",
932
- message: `Invalid og frontmatter field "${field}" for ${absolutePath}`,
933
- details: { path: absolutePath, field: `og.${field}` }
934
- });
935
- let og = {};
936
- if (source.title !== void 0) {
937
- if (typeof source.title != "string" || source.title.length === 0)
938
- throw new AppError2({
939
- code: "CONTENT_FRONTMATTER_OG_INVALID",
940
- message: `Invalid og.title for ${absolutePath}; expected a non-empty string`,
941
- details: { path: absolutePath, field: "og.title" }
942
- });
943
- og.title = source.title;
944
- }
945
- if (source.description !== void 0) {
946
- if (typeof source.description != "string" || source.description.length === 0)
947
- throw new AppError2({
948
- code: "CONTENT_FRONTMATTER_OG_INVALID",
949
- message: `Invalid og.description for ${absolutePath}; expected a non-empty string`,
950
- details: { path: absolutePath, field: "og.description" }
951
- });
952
- og.description = source.description;
953
- }
954
- if (source.image !== void 0)
955
- if (typeof source.image == "string" && source.image.length > 0)
956
- og.image = source.image;
957
- else if (source.image && typeof source.image == "object" && !Array.isArray(source.image)) {
958
- let image = source.image, allowedImageFields = /* @__PURE__ */ new Set(["src", "alt"]);
959
- for (let field of Object.keys(image))
960
- if (!allowedImageFields.has(field))
961
- throw new AppError2({
962
- code: "CONTENT_FRONTMATTER_OG_INVALID",
963
- message: `Invalid og.image field "${field}" for ${absolutePath}`,
964
- details: { path: absolutePath, field: `og.image.${field}` }
965
- });
966
- if (typeof image.src != "string" || image.src.length === 0)
967
- throw new AppError2({
968
- code: "CONTENT_FRONTMATTER_OG_INVALID",
969
- message: `Invalid og.image.src for ${absolutePath}; expected a non-empty string`,
970
- details: { path: absolutePath, field: "og.image.src" }
971
- });
972
- if (image.alt !== void 0 && typeof image.alt != "string")
973
- throw new AppError2({
974
- code: "CONTENT_FRONTMATTER_OG_INVALID",
975
- message: `Invalid og.image.alt for ${absolutePath}; expected a string`,
976
- details: { path: absolutePath, field: "og.image.alt" }
977
- });
978
- og.image = {
979
- src: image.src,
980
- ...typeof image.alt == "string" ? { alt: image.alt } : {}
981
- };
982
- } else
983
- throw new AppError2({
984
- code: "CONTENT_FRONTMATTER_OG_INVALID",
985
- message: `Invalid og.image for ${absolutePath}; expected a non-empty string or object with src`,
986
- details: { path: absolutePath, field: "og.image" }
987
- });
988
- return Object.keys(og).length > 0 ? og : void 0;
989
- }
990
- function customMeta(frontmatter, absolutePath) {
991
- let meta = {}, warnings = [];
992
- for (let [key, value] of Object.entries(frontmatter))
993
- if (!RESERVED_FIELDS.has(key)) {
994
- if (UNSUPPORTED_FIELDS.has(key)) {
934
+ async function readContentFile(group, absolutePath) {
935
+ let raw = await readFile3(absolutePath, "utf-8");
936
+ try {
937
+ let parsed = parseSourceFrontmatter(raw), frontmatter = parsed.data, relativePath = relative2(group.contentPath, absolutePath), fileID = normalizeContentSlug(fileSlug(relativePath), absolutePath), warnings = [];
938
+ if (parsed.content.includes("<")) {
939
+ let unknown = new Set(
940
+ convertMdxToMarkdown({ source: parsed.content }).diagnostics.filter(
941
+ (diagnostic) => diagnostic.code === "mdx-component-unknown"
942
+ ).map((diagnostic) => diagnostic.componentName).filter((name) => !!name)
943
+ );
944
+ for (let name of unknown)
995
945
  warnings.push(
996
946
  warning(
997
- "CONTENT_FRONTMATTER_FIELD_UNSUPPORTED",
998
- `Ignoring unsupported frontmatter field "${key}" for ${absolutePath}`,
999
- { path: absolutePath, field: key }
947
+ "CONTENT_COMPONENT_NOT_CONVERTED",
948
+ `Custom component <${name}> renders in HTML, but its runtime output is not included in generated Markdown or search. Authored children remain available.`,
949
+ { path: absolutePath }
1000
950
  )
1001
951
  );
1002
- continue;
1003
- }
1004
- meta[key] = value;
1005
952
  }
1006
- return { meta, warnings };
1007
- }
1008
- async function readContentFile(group, absolutePath, configSlugs) {
1009
- let raw = await readFile3(absolutePath, "utf-8");
1010
- try {
1011
- let parsed = matter(raw), frontmatter = parsed.data, relativePath = relative2(group.contentPath, absolutePath), fileID = normalizeContentSlug(fileSlug(relativePath), absolutePath), title = asString(frontmatter.title);
1012
- if (frontmatter.id !== void 0)
1013
- throw new AppError2({
1014
- code: ERROR_CODES.CONTENT_FRONTMATTER_ID_INVALID,
1015
- message: `Frontmatter field "id" is not supported for ${absolutePath}; page identity is derived from fileID`,
1016
- details: { path: absolutePath, field: "id" }
1017
- });
953
+ for (let field of ["title", "description"])
954
+ if (frontmatter[field] !== void 0 && typeof frontmatter[field] != "string")
955
+ throw new AppError2({
956
+ code: "CONTENT_FRONTMATTER_TYPE_INVALID",
957
+ message: `Invalid frontmatter field "${field}" in ${absolutePath}; expected a string. Remove it to use the default or provide text.`,
958
+ details: { path: absolutePath, field }
959
+ });
960
+ let title = frontmatter.title?.trim() || defaultTitle(relativePath), description = frontmatter.description?.trim() || defaultDescription(parsed.content);
961
+ for (let field of ["title", "description"])
962
+ frontmatter[field]?.trim() || warnings.push(
963
+ warning(
964
+ "CONTENT_FRONTMATTER_DEFAULT",
965
+ `Missing or blank ${field} in ${absolutePath}; add ${field} to frontmatter.`,
966
+ { path: absolutePath, field }
967
+ )
968
+ );
1018
969
  if (!fileID)
1019
970
  throw new AppError2({
1020
971
  code: ERROR_CODES.CONTENT_FRONTMATTER_ID_INVALID,
1021
972
  message: `Could not derive fileID for ${absolutePath}`,
1022
973
  details: { path: absolutePath }
1023
974
  });
1024
- if (!title)
975
+ if (frontmatter.slug !== void 0 && (typeof frontmatter.slug != "string" || !frontmatter.slug.trim() || /[\\/]/.test(frontmatter.slug)))
1025
976
  throw new AppError2({
1026
- code: ERROR_CODES.CONTENT_FRONTMATTER_REQUIRED,
1027
- message: `Missing required frontmatter field "title" for ${absolutePath}`,
1028
- details: { path: absolutePath, field: "title" }
977
+ code: ERROR_CODES.CONTENT_SLUG_INVALID,
978
+ message: `Invalid slug in ${absolutePath}; use one nonempty path segment, or remove slug to use the filename.`,
979
+ details: {
980
+ path: absolutePath,
981
+ field: "slug",
982
+ correction: "Remove slug or provide one nonempty path segment."
983
+ }
1029
984
  });
1030
- let warnings = [], frontmatterSlug = asString(frontmatter.slug), slugSource = configSlugs.get(fileID) ?? frontmatterSlug ?? fileID, slugResult = normalizeSlugPath(slugSource);
1031
- if (slugResult.errors.length > 0)
985
+ let sourceSegments = fileID.split("/"), slugSource = frontmatter.slug ?? sourceSegments.at(-1) ?? "", slugResult = normalizeSlugPath(slugSource);
986
+ if (slugResult.errors.length > 0 || !slugResult.value || slugResult.value.includes("/"))
1032
987
  throw new AppError2({
1033
988
  code: ERROR_CODES.CONTENT_SLUG_INVALID,
1034
- message: `Invalid content slug "${slugSource}" for ${absolutePath}`,
989
+ message: `Invalid slug "${slugSource}" in ${absolutePath}; remove it or provide one valid path segment.`,
1035
990
  details: {
1036
991
  errors: slugResult.errors,
1037
992
  path: absolutePath,
1038
- slug: slugSource
993
+ field: "slug",
994
+ correction: "Remove slug or provide one valid path segment."
1039
995
  }
1040
996
  });
1041
- let slug = slugResult.value;
1042
- slugResult.changed && warnings.push(
997
+ let slug = slugResult.value, routeSlug = [...sourceSegments.slice(0, -1), slug].join("/");
998
+ if (slugResult.changed && warnings.push(
1043
999
  warning(
1044
1000
  "CONTENT_SLUG_NORMALIZED",
1045
1001
  `Normalized slug "${slugSource}" to "${slug}" for ${absolutePath}`,
1046
1002
  { path: absolutePath, from: slugSource, to: slug }
1047
1003
  )
1048
- );
1049
- let status = "published";
1050
- frontmatter.status === "published" || frontmatter.status === "draft" || frontmatter.status === "archived" ? status = frontmatter.status : frontmatter.status !== void 0 && warnings.push(
1004
+ ), frontmatter.status !== void 0 && typeof frontmatter.status != "string")
1005
+ throw new AppError2({
1006
+ code: "CONTENT_FRONTMATTER_TYPE_INVALID",
1007
+ message: `Invalid frontmatter field "status" in ${absolutePath}; expected a string.`,
1008
+ details: {
1009
+ path: absolutePath,
1010
+ field: "status",
1011
+ correction: 'Set status to "draft" or "published", or remove it.'
1012
+ }
1013
+ });
1014
+ let normalizedStatus = frontmatter.status?.trim().toLowerCase(), status = "published";
1015
+ normalizedStatus === "published" || normalizedStatus === "draft" ? status = normalizedStatus : frontmatter.status !== void 0 && warnings.push(
1051
1016
  warning(
1052
1017
  "CONTENT_STATUS_INVALID",
1053
- `Invalid status "${String(frontmatter.status)}" for ${absolutePath}; defaulting to published`,
1054
- { path: absolutePath, status: frontmatter.status }
1055
- )
1056
- );
1057
- let rawPublishedAt = asDateString(frontmatter.publishedAt), publishedAt = rawPublishedAt && isIsoDate(rawPublishedAt) ? rawPublishedAt : void 0;
1058
- rawPublishedAt && !publishedAt && warnings.push(
1059
- warning(
1060
- "CONTENT_DATE_INVALID",
1061
- `Invalid publishedAt for ${absolutePath}`,
1018
+ `Status "${String(frontmatter.status)}" is not recognized; this page will be published.`,
1062
1019
  {
1063
1020
  path: absolutePath,
1064
- field: "publishedAt",
1065
- value: rawPublishedAt
1021
+ propertyPath: "status",
1022
+ correction: 'Set status to "draft" to keep the page unpublished, or "published" to publish it.'
1066
1023
  }
1067
1024
  )
1068
1025
  );
1069
- let rawUpdatedAt = asDateString(frontmatter.updatedAt), updatedAt = rawUpdatedAt && isIsoDate(rawUpdatedAt) ? rawUpdatedAt : void 0;
1070
- rawUpdatedAt && !updatedAt && warnings.push(
1071
- warning(
1072
- "CONTENT_DATE_INVALID",
1073
- `Invalid updatedAt for ${absolutePath}`,
1074
- {
1026
+ let metadataResult = pageMetadataSchema.safeParse(
1027
+ frontmatter.metadata === void 0 ? {} : frontmatter.metadata
1028
+ );
1029
+ if (!metadataResult.success) {
1030
+ let issue = metadataResult.error.issues[0], key = issue?.code === "unrecognized_keys" ? issue.keys[0] : void 0, field = [
1031
+ "metadata",
1032
+ ...issue?.path ?? [],
1033
+ ...key ? [key] : []
1034
+ ].join(".");
1035
+ throw new AppError2({
1036
+ code: "CONTENT_FRONTMATTER_TYPE_INVALID",
1037
+ message: `Invalid ${field} in ${absolutePath}. ${formatConfigIssues(metadataResult.error)}. Correct or remove this field.`,
1038
+ details: {
1075
1039
  path: absolutePath,
1076
- field: "updatedAt",
1077
- value: rawUpdatedAt
1040
+ field,
1041
+ correction: `Correct or remove ${field}.`
1078
1042
  }
1079
- )
1080
- );
1081
- let authors = normalizeAuthorship(frontmatter.authors, absolutePath);
1082
- warnings.push(...authors.warnings);
1083
- let tags = normalizeTags(frontmatter.tags, absolutePath);
1084
- warnings.push(...tags.warnings);
1085
- let og = normalizeOgMetadata(frontmatter.og, absolutePath), meta = customMeta(frontmatter, absolutePath);
1086
- return warnings.push(...meta.warnings), {
1043
+ });
1044
+ }
1045
+ let resolved = {
1046
+ ...frontmatter,
1047
+ title,
1048
+ description,
1049
+ slug,
1050
+ status,
1051
+ metadata: metadataResult.data
1052
+ };
1053
+ return {
1087
1054
  fileID,
1088
1055
  group: group.key,
1089
- slug,
1090
- title,
1091
- description: asString(frontmatter.description),
1092
- og,
1056
+ slug: routeSlug,
1057
+ frontmatter: resolved,
1093
1058
  status,
1094
- publishedAt,
1095
- updatedAt,
1096
- authors: authors.authors,
1097
- tags: tags.tags,
1098
- meta: meta.meta,
1099
1059
  absolutePath,
1100
1060
  relativePath,
1101
- body: parsed.content.trim(),
1061
+ body: parsed.content,
1102
1062
  warnings
1103
1063
  };
1104
1064
  } catch (error) {
1105
1065
  throw error instanceof AppError2 ? error : new AppError2({
1106
1066
  code: ERROR_CODES.CONTENT_FILE_PARSE_FAILED,
1107
- message: `Failed to parse frontmatter for ${absolutePath}`,
1108
- details: { error }
1067
+ message: `Failed to parse page ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`,
1068
+ details: {
1069
+ path: absolutePath,
1070
+ ...error instanceof FrontmatterParseError ? { line: error.line, column: error.column, field: error.field } : {},
1071
+ error
1072
+ }
1109
1073
  });
1110
1074
  }
1111
1075
  }
1112
1076
  async function walkContent(group) {
1113
- let files = [], stack = [group.contentPath], configSlugs = treeSlugs(group.tree);
1077
+ let files = [], stack = [group.contentPath];
1114
1078
  for (; stack.length > 0; ) {
1115
1079
  let current = stack.pop();
1116
1080
  if (!current) continue;
@@ -1128,15 +1092,15 @@ async function walkContent(group) {
1128
1092
  stack.push(absolute);
1129
1093
  continue;
1130
1094
  }
1131
- extname(entry).toLowerCase() === ".mdx" && files.push(await readContentFile(group, absolute, configSlugs));
1095
+ extname2(entry).toLowerCase() === ".mdx" && files.push(await readContentFile(group, absolute));
1132
1096
  }
1133
1097
  }
1134
1098
  return files;
1135
1099
  }
1136
- function buildRoutes(groups, files) {
1100
+ function buildRoutes(groups, files, includeDrafts = !1) {
1137
1101
  let routes = [];
1138
1102
  for (let file of files) {
1139
- if (file.status === "draft")
1103
+ if (file.status === "draft" && !includeDrafts)
1140
1104
  continue;
1141
1105
  let group = groups[file.group];
1142
1106
  if (!group)
@@ -1154,50 +1118,75 @@ function buildRoutes(groups, files) {
1154
1118
  async function scanContent(config) {
1155
1119
  let files = (await Promise.all(
1156
1120
  Object.values(config.groups).map((group) => walkContent(group))
1157
- )).flat(), warnings = files.flatMap((file) => file.warnings), fileIDs = /* @__PURE__ */ new Set();
1121
+ )).flat(), warnings = files.flatMap((file) => file.warnings), fileIDs = /* @__PURE__ */ new Map();
1158
1122
  for (let file of files) {
1159
- let scopedFileID = `${file.group}:${file.fileID}`;
1160
- if (fileIDs.has(scopedFileID))
1123
+ let scopedFileID = `${file.group}:${file.fileID}`, previous = fileIDs.get(scopedFileID), sourceRoot = config.groups[file.group]?.__meta.basePath ?? "";
1124
+ if (previous)
1161
1125
  throw new AppError2({
1162
1126
  code: ERROR_CODES.CONTENT_DUPLICATE_ID,
1163
- message: `Duplicate content fileID "${file.fileID}" in group ${file.group}`,
1164
- details: { fileID: file.fileID, group: file.group }
1127
+ message: `Duplicate page identity "${file.fileID}" in group "${file.group}": ${relative2(sourceRoot, previous.absolutePath)} and ${relative2(sourceRoot, file.absolutePath)}. Rename one source file.`,
1128
+ details: {
1129
+ path: file.absolutePath,
1130
+ existingPath: previous.absolutePath,
1131
+ fileID: file.fileID,
1132
+ group: file.group,
1133
+ correction: "Rename one source file."
1134
+ }
1165
1135
  });
1166
- fileIDs.add(scopedFileID);
1136
+ fileIDs.set(scopedFileID, file);
1167
1137
  }
1168
- let routes = buildRoutes(config.groups, files), routePaths = /* @__PURE__ */ new Set(), groupPaths = new Map(
1169
- Object.values(config.groups).map((group) => [group.path, group.key])
1170
- );
1171
- for (let route of routes) {
1172
- if (routePaths.has(route.path))
1173
- throw new AppError2({
1174
- code: ERROR_CODES.CONTENT_DUPLICATE_ROUTE,
1175
- message: `Duplicate route "${route.path}"`,
1176
- details: { route: route.path }
1177
- });
1178
- let groupPathOwner = groupPaths.get(route.path);
1179
- if (groupPathOwner)
1138
+ let candidates = buildRoutes(config.groups, files, !0), routes = buildRoutes(config.groups, files), owners = /* @__PURE__ */ new Map();
1139
+ for (let group of Object.values(config.groups))
1140
+ owners.set(group.path, {
1141
+ source: relative2(
1142
+ group.__meta.basePath,
1143
+ group.__meta.filePath ?? group.__meta.basePath
1144
+ ),
1145
+ kind: `group ${group.key}`
1146
+ });
1147
+ for (let route of candidates) {
1148
+ let file = fileIDs.get(`${route.group}:${route.fileID}`);
1149
+ if (!file) continue;
1150
+ let sourceRoot = config.groups[route.group]?.__meta.basePath ?? "", sourcePath = relative2(sourceRoot, file.absolutePath), paths = [[route.path, "page"]];
1151
+ config.agent?.enabled !== !1 && config.agent?.markdownRoutes !== !1 && paths.push([`${route.path}.md`, "Markdown"]);
1152
+ let normalized = normalizeSlugPath(route.path.replace(/^\//, ""));
1153
+ if (normalized.errors.length)
1180
1154
  throw new AppError2({
1181
- code: ERROR_CODES.CONTENT_DUPLICATE_ROUTE,
1182
- message: `Content route "${route.path}" collides with group "${groupPathOwner}"`,
1155
+ code: ERROR_CODES.CONTENT_SLUG_INVALID,
1156
+ message: `Invalid route "${route.path}" for ${file.absolutePath}. Shorten the path.`,
1183
1157
  details: {
1158
+ path: file.absolutePath,
1184
1159
  route: route.path,
1185
- group: route.group,
1186
- fileID: route.fileID,
1187
- existingGroup: groupPathOwner
1160
+ errors: normalized.errors,
1161
+ correction: "Shorten the path."
1188
1162
  }
1189
1163
  });
1190
- routePaths.add(route.path);
1164
+ for (let [path, kind] of paths) {
1165
+ let previous = owners.get(path);
1166
+ if (previous)
1167
+ throw new AppError2({
1168
+ code: ERROR_CODES.CONTENT_DUPLICATE_ROUTE,
1169
+ message: `URL "${path}" is used by ${previous.kind} ${previous.source} and ${kind} ${sourcePath}. Change a page slug or group path.`,
1170
+ details: {
1171
+ path: file.absolutePath,
1172
+ existingPath: previous.source,
1173
+ route: path,
1174
+ correction: "Change a page slug or group path."
1175
+ }
1176
+ });
1177
+ owners.set(path, { source: sourcePath, kind });
1178
+ }
1191
1179
  }
1192
1180
  let meta = config.meta;
1193
1181
  return {
1194
1182
  site: {
1195
1183
  name: config.site?.name ?? meta?.title ?? "Untitled",
1196
1184
  description: config.site?.description ?? meta?.description,
1197
- ...config.og ? { og: config.og } : {}
1185
+ ...config.metadata ? { metadata: config.metadata } : {}
1198
1186
  },
1199
1187
  files,
1200
1188
  routes,
1189
+ candidateRoutes: candidates,
1201
1190
  warnings
1202
1191
  };
1203
1192
  }
@@ -1216,13 +1205,26 @@ function appErrorToDiagnostic(error) {
1216
1205
  import { readFile as readFile4 } from "fs/promises";
1217
1206
  import { relative as relative3, resolve as resolve4 } from "path";
1218
1207
  async function missingPageReferences(options) {
1219
- let diagnostics = [], configSource = /* @__PURE__ */ new Map(), routes = new Set(
1220
- options.content.routes.map((route) => `${route.group}\0${route.fileID}`)
1208
+ let diagnostics = [], configSource = /* @__PURE__ */ new Map(), files = new Map(
1209
+ options.content.files.map(
1210
+ (file) => [
1211
+ relative3(options.sourceRoot, file.absolutePath).replace(/\\/g, "/"),
1212
+ file
1213
+ ]
1214
+ )
1215
+ ), groupContentRoots = Object.values(options.config.groups).map(
1216
+ (group) => ({
1217
+ group,
1218
+ prefix: `${group.__meta.relativeContentPath.replace(/\\/g, "/")}/`
1219
+ })
1221
1220
  ), rootConfig = resolve4(options.sourceRoot, "publish.os.config.json");
1222
- async function locationFor(groupKey, groupFile, path) {
1221
+ function groupForSourcePath(path) {
1222
+ return groupContentRoots.filter(({ prefix }) => path.startsWith(prefix)).sort((a, b) => b.prefix.length - a.prefix.length)[0]?.group;
1223
+ }
1224
+ async function locationFor(groupFile, path, rootPath) {
1223
1225
  for (let [file, sourcePath] of [
1224
1226
  [groupFile, path],
1225
- [rootConfig, ["groups", groupKey, ...path]]
1227
+ [rootConfig, rootPath]
1226
1228
  ]) {
1227
1229
  let raw = configSource.get(file);
1228
1230
  raw === void 0 && (raw = await readFile4(file, "utf8"), configSource.set(file, raw));
@@ -1235,227 +1237,491 @@ async function missingPageReferences(options) {
1235
1237
  }
1236
1238
  }
1237
1239
  for (let group of Object.values(options.config.groups)) {
1238
- async function visit(nodes, path) {
1240
+ let hasLocalNavigation = !!group.localNavigation, basePath = hasLocalNavigation ? ["navigation", "items"] : ["groups", group.key, "navigation", "items"], rootBasePath = hasLocalNavigation ? ["groups", group.key, ...basePath] : basePath;
1241
+ async function visit(nodes, path, rootPath, contentGroup = group) {
1239
1242
  for (let [index, node] of nodes.entries()) {
1240
- let nodePath = [...path, index];
1241
- if (node.type === "page" && !routes.has(`${group.key}\0${node.file}`)) {
1242
- let propertyPath = `groups.${group.key}.${nodePath.join(".")}.file`, location = await locationFor(group.key, group.__meta.filePath, [
1243
- ...nodePath,
1244
- "file"
1245
- ]), expected = `publish.os/${group.__meta.relativeContentPath.replace(/\\/g, "/")}/${node.file}.mdx`;
1246
- diagnostics.push({
1247
- code: "CONTENT_TREE_PAGE_MISSING",
1248
- level: "warning",
1249
- message: `Referenced page "${node.file}" was not found. Expected ${expected}.`,
1250
- details: {
1251
- ...location,
1252
- propertyPath,
1253
- correction: "Add that page, or change or remove the tree entry."
1254
- }
1255
- });
1243
+ let nodePath = [...path, index], rootNodePath = [...rootPath, index], targetGroupKey = node.type === "folder" || node.type === "page" ? node.group : void 0, nestedContentGroup = targetGroupKey ? options.config.groups[targetGroupKey] ?? contentGroup : contentGroup;
1244
+ if (node.type === "page" && node.file) {
1245
+ let expected = node.file.replace(/\\/g, "/"), propertyPath = `${nodePath.join(".")}.file`, location = await locationFor(
1246
+ group.__meta.filePath,
1247
+ [...nodePath, "file"],
1248
+ [...rootNodePath, "file"]
1249
+ ), expectedPrefix = `${contentGroup.__meta.relativeContentPath.replace(/\\/g, "/")}/`, referencedFile = files.get(expected), targetGroupKey2 = (referencedFile ? options.config.groups[referencedFile.group] : groupForSourcePath(expected))?.key ?? referencedFile?.group, isForeign = targetGroupKey2 !== void 0 && targetGroupKey2 !== contentGroup.key;
1250
+ if (isForeign || !referencedFile) {
1251
+ let code = isForeign ? "NAVIGATION_PAGE_FOREIGN" : "NAVIGATION_PAGE_MISSING", message = isForeign ? `Referenced page "${node.file}" belongs to group "${targetGroupKey2}", not "${contentGroup.key}".` : `Referenced page "${node.file}" was not found.`;
1252
+ diagnostics.push({
1253
+ code,
1254
+ level: "error",
1255
+ message,
1256
+ details: {
1257
+ ...location,
1258
+ propertyPath,
1259
+ group: contentGroup.key,
1260
+ target: node.file,
1261
+ ...targetGroupKey2 ? { owner: targetGroupKey2 } : {},
1262
+ correction: `Use a source-root-relative MDX path inside ${expectedPrefix}, such as ${expectedPrefix}<file>.mdx.`
1263
+ }
1264
+ });
1265
+ }
1256
1266
  }
1257
- "children" in node && node.children && await visit(node.children, [...nodePath, "children"]);
1267
+ "items" in node && node.items && await visit(
1268
+ node.items,
1269
+ [...nodePath, "items"],
1270
+ [...rootNodePath, "items"],
1271
+ nestedContentGroup
1272
+ ), (node.type === "folder" || node.type === "page") && node.navigation && await visit(
1273
+ node.navigation.items,
1274
+ [...nodePath, "navigation", "items"],
1275
+ [...rootNodePath, "navigation", "items"],
1276
+ nestedContentGroup
1277
+ );
1258
1278
  }
1259
1279
  }
1260
- await visit(group.tree ?? [], ["tree"]);
1280
+ let navigation = group.localNavigation ?? group.navigation;
1281
+ await visit(navigation?.items ?? [], basePath, rootBasePath);
1261
1282
  }
1262
1283
  return diagnostics;
1263
1284
  }
1264
1285
 
1265
1286
  // src/lib/compiler/source/manifest/create-publish-os-manifest.ts
1266
1287
  import { createHash } from "crypto";
1288
+
1289
+ // src/lib/compiler/source/manifest/sort-navigation-entries.ts
1290
+ function sortKey(value, type) {
1291
+ if (value === void 0) return { reason: "missing" };
1292
+ if (type === "number")
1293
+ return typeof value == "number" && Number.isFinite(value) ? { value } : { reason: "invalid" };
1294
+ let date = authoredDate(value);
1295
+ return date ? { value: Date.parse(date) } : { reason: "invalid" };
1296
+ }
1297
+ function filenameOrder(left, right) {
1298
+ return left.localeCompare(right);
1299
+ }
1300
+ function sortNavigationEntries({
1301
+ entries,
1302
+ explicitCount = 0,
1303
+ field,
1304
+ type,
1305
+ direction
1306
+ }) {
1307
+ if (!Number.isInteger(explicitCount) || explicitCount < 0 || explicitCount > entries.length)
1308
+ throw new RangeError(
1309
+ "explicitCount must be between zero and the entry count."
1310
+ );
1311
+ let automatic = entries.slice(explicitCount), pageEntries = automatic.flatMap(
1312
+ (candidate, index) => candidate.kind === "page" ? [{ candidate, index, key: sortKey(candidate.frontmatter[field], type) }] : []
1313
+ ), warnings = pageEntries.flatMap(
1314
+ ({ candidate, key }) => key.reason ? [
1315
+ {
1316
+ code: key.reason === "missing" ? "NAVIGATION_SORT_VALUE_MISSING" : "NAVIGATION_SORT_VALUE_INVALID",
1317
+ field,
1318
+ file: candidate.sourcePath
1319
+ }
1320
+ ] : []
1321
+ ), directionFactor = direction === "asc" ? 1 : -1;
1322
+ pageEntries.sort((left, right) => {
1323
+ let leftValue = left.key.value, rightValue = right.key.value, leftValid = leftValue !== void 0;
1324
+ return leftValid !== (rightValue !== void 0) ? leftValid ? -1 : 1 : leftValue !== void 0 && rightValue !== void 0 && leftValue !== rightValue ? (leftValue - rightValue) * directionFactor : filenameOrder(
1325
+ left.candidate.sourcePath,
1326
+ right.candidate.sourcePath
1327
+ ) || left.index - right.index;
1328
+ });
1329
+ let nextPage = 0, sortedAutomatic = automatic.map(
1330
+ (candidate) => candidate.kind === "page" ? pageEntries[nextPage++]?.candidate : candidate
1331
+ );
1332
+ return {
1333
+ entries: [
1334
+ ...entries.slice(0, explicitCount).map((candidate) => candidate.entry),
1335
+ ...sortedAutomatic.flatMap(
1336
+ (candidate) => candidate ? [candidate.entry] : []
1337
+ )
1338
+ ],
1339
+ warnings
1340
+ };
1341
+ }
1342
+
1343
+ // src/lib/compiler/source/manifest/create-publish-os-manifest.ts
1267
1344
  function stableStringify(value) {
1268
1345
  return Array.isArray(value) ? `[${value.map((item) => stableStringify(item)).join(",")}]` : value && typeof value == "object" ? `{${Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(
1269
1346
  ([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`
1270
1347
  ).join(",")}}` : JSON.stringify(value);
1271
1348
  }
1272
- function resolveGroupReference(config, currentGroup, reference) {
1273
- let relativeKey = `${currentGroup}/${reference}`;
1274
- if (config.groups[relativeKey]) return relativeKey;
1275
- if (config.groups[reference]) return reference;
1276
- }
1277
- function normalizeTreeNode(node, group, pagesByFileID, routesByFileID, options) {
1278
- if (node.type === "page") {
1279
- let page = pagesByFileID.get(node.file), route = routesByFileID.get(node.file);
1280
- return !page || !route ? null : {
1281
- type: "page",
1282
- fileID: node.file,
1283
- title: node.title ?? page.title,
1284
- path: route.path,
1285
- group,
1286
- description: page.description,
1287
- og: page.og,
1288
- status: page.status,
1289
- publishedAt: page.publishedAt,
1290
- updatedAt: page.updatedAt,
1291
- authors: page.authors,
1292
- tags: page.tags,
1293
- meta: page.meta
1294
- };
1295
- }
1296
- if (node.type === "link")
1297
- return {
1298
- type: "link",
1299
- id: node.id,
1300
- title: node.title,
1301
- href: node.url
1302
- };
1303
- let children = node.children?.map(
1304
- (child) => normalizeTreeNode(child, group, pagesByFileID, routesByFileID, options)
1305
- ).filter((child) => child !== null) ?? [];
1306
- if (node.type === "folder") {
1307
- let referencedGroup = resolveGroupReference(
1308
- options.config,
1309
- group,
1310
- node.group ?? node.id
1311
- ), referencedChildren = children.length > 0 || !referencedGroup ? children : options.childTreeForGroup(referencedGroup);
1312
- return {
1313
- type: "folder",
1314
- id: node.id,
1315
- title: node.title,
1316
- path: referencedGroup ? options.config.groups[referencedGroup]?.path : void 0,
1317
- group: referencedGroup ?? group,
1318
- children: referencedChildren
1319
- };
1320
- }
1321
- return {
1322
- type: node.type,
1323
- id: node.id,
1324
- title: node.title,
1325
- group,
1326
- children
1327
- };
1349
+ function nodeId(group, source, type) {
1350
+ return createHash("sha256").update(`${group}\0${type}\0${source}`).digest("hex").slice(0, 16);
1328
1351
  }
1329
- function defaultTree(group, pages, routesByFileID) {
1330
- return pages.flatMap((page) => {
1331
- let route = routesByFileID.get(page.fileID);
1332
- return route ? {
1333
- type: "page",
1334
- fileID: page.fileID,
1335
- title: page.title,
1336
- path: route.path,
1352
+ function navigationError(message, group, target, source) {
1353
+ return new AppError2({
1354
+ code: ERROR_CODES.CONFIG_GROUP_INVALID_SCHEMA,
1355
+ message,
1356
+ details: {
1337
1357
  group,
1338
- description: page.description,
1339
- og: page.og,
1340
- status: page.status,
1341
- publishedAt: page.publishedAt,
1342
- updatedAt: page.updatedAt,
1343
- authors: page.authors,
1344
- tags: page.tags,
1345
- meta: page.meta
1346
- } : [];
1347
- }).sort((a, b) => a.path.localeCompare(b.path));
1348
- }
1349
- function configuredPageIds(nodes) {
1350
- let ids = /* @__PURE__ */ new Set();
1351
- for (let node of nodes ?? []) {
1352
- if (node.type === "page") {
1353
- ids.add(node.file);
1354
- continue;
1358
+ target,
1359
+ ...source ? { propertyPath: source } : {},
1360
+ correction: "Correct the navigation entry and save the group config."
1361
+ }
1362
+ });
1363
+ }
1364
+ function defaultItems(options) {
1365
+ let remaining = new Map(options.pages.map((page) => [page.fileID, page])), sortWarningReported = !1;
1366
+ function folderItems(folder) {
1367
+ let names = /* @__PURE__ */ new Set();
1368
+ for (let fileID of remaining.keys()) {
1369
+ if (folder && !fileID.startsWith(`${folder}/`)) continue;
1370
+ let name = fileID.slice(folder ? folder.length + 1 : 0);
1371
+ names.add(name.split("/")[0] ?? name);
1372
+ }
1373
+ let items2 = [];
1374
+ for (let name of [...names].sort((a, b) => a.localeCompare(b))) {
1375
+ let prefix = folder ? `${folder}/${name}` : name, file = remaining.get(prefix);
1376
+ if (file) {
1377
+ remaining.delete(prefix);
1378
+ let route = options.routes.get(file.fileID);
1379
+ if (!route) continue;
1380
+ items2.push({
1381
+ id: nodeId(options.group, file.fileID, "page"),
1382
+ type: "page",
1383
+ label: file.frontmatter.title,
1384
+ href: route.path,
1385
+ page: {
1386
+ fileID: file.fileID,
1387
+ group: file.group,
1388
+ path: file.path,
1389
+ frontmatter: file.frontmatter
1390
+ }
1391
+ });
1392
+ continue;
1393
+ }
1394
+ let children = folderItems(prefix);
1395
+ children.length && items2.push({
1396
+ id: nodeId(options.group, prefix, "folder"),
1397
+ type: "folder",
1398
+ label: name,
1399
+ children
1400
+ });
1355
1401
  }
1356
- if ("children" in node)
1357
- for (let id of configuredPageIds(node.children))
1358
- ids.add(id);
1402
+ return sortItems(items2);
1403
+ }
1404
+ let items = folderItems("");
1405
+ for (let child of options.childGroups)
1406
+ items.push(options.makeGroup(child, "page"));
1407
+ return items;
1408
+ function sortItems(items2) {
1409
+ let sort = options.sort;
1410
+ if (!sort)
1411
+ return options.sortWarning && !sortWarningReported && (sortWarningReported = !0, options.onWarning?.({
1412
+ code: "NAVIGATION_SORT_CONFIG_INVALID",
1413
+ level: "warning",
1414
+ message: `Group "${options.group}": ${options.sortWarning}`,
1415
+ details: {
1416
+ group: options.group,
1417
+ propertyPath: "navigation.sort"
1418
+ }
1419
+ })), items2;
1420
+ let result = sortNavigationEntries({
1421
+ entries: items2.map(
1422
+ (entry) => entry.type === "page" ? {
1423
+ kind: "page",
1424
+ entry,
1425
+ sourcePath: `${options.contentPath.replace(/\\/g, "/")}/${entry.page.fileID}.mdx`,
1426
+ frontmatter: entry.page.frontmatter
1427
+ } : { kind: "other", entry }
1428
+ ),
1429
+ field: sort.field,
1430
+ type: sort.type,
1431
+ direction: sort.direction
1432
+ });
1433
+ for (let warning2 of result.warnings)
1434
+ options.onWarning?.({
1435
+ code: warning2.code,
1436
+ level: "warning",
1437
+ message: warning2.code === "NAVIGATION_SORT_VALUE_MISSING" ? `Navigation sort field "${warning2.field}" is missing from ${warning2.file}; the page sorts after valid values.` : `Navigation sort field "${warning2.field}" is invalid in ${warning2.file}; the page sorts after valid values.`,
1438
+ details: {
1439
+ group: options.group,
1440
+ field: warning2.field,
1441
+ file: warning2.file
1442
+ }
1443
+ });
1444
+ return result.entries;
1359
1445
  }
1360
- return ids;
1361
1446
  }
1362
- function createManifest(config, content) {
1447
+ function createManifest(config, content, onNavigationWarning) {
1363
1448
  let routesByGroup = /* @__PURE__ */ new Map();
1364
1449
  for (let route of content.routes) {
1365
1450
  let groupRoutes = routesByGroup.get(route.group);
1366
1451
  groupRoutes || (groupRoutes = /* @__PURE__ */ new Map(), routesByGroup.set(route.group, groupRoutes)), groupRoutes.has(route.fileID) || groupRoutes.set(route.fileID, route);
1367
1452
  }
1368
- let pages = [], pagesByGroup = /* @__PURE__ */ new Map(), pagesByFileIDByGroup = /* @__PURE__ */ new Map();
1453
+ let pages = [], pagesByGroup = /* @__PURE__ */ new Map(), sourcePathToPage = /* @__PURE__ */ new Map();
1369
1454
  for (let file of content.files) {
1370
1455
  let route = routesByGroup.get(file.group)?.get(file.fileID);
1371
1456
  if (!route) continue;
1372
1457
  let page = {
1373
1458
  fileID: file.fileID,
1374
1459
  group: file.group,
1375
- slug: route.slug,
1376
1460
  path: route.path,
1377
- title: file.title,
1378
- description: file.description,
1379
- og: file.og,
1380
- status: file.status,
1381
- publishedAt: file.publishedAt,
1382
- updatedAt: file.updatedAt,
1383
- authors: file.authors,
1384
- tags: file.tags,
1385
- meta: file.meta,
1461
+ frontmatter: file.frontmatter,
1386
1462
  body: file.body
1387
1463
  };
1388
1464
  pages.push(page);
1389
1465
  let groupPages = pagesByGroup.get(page.group);
1390
1466
  groupPages ? groupPages.push(page) : pagesByGroup.set(page.group, [page]);
1391
- let pagesByFileID = pagesByFileIDByGroup.get(page.group);
1392
- pagesByFileID || (pagesByFileID = /* @__PURE__ */ new Map(), pagesByFileIDByGroup.set(page.group, pagesByFileID)), pagesByFileID.has(page.fileID) || pagesByFileID.set(page.fileID, page);
1467
+ let group = config.groups[file.group];
1468
+ if (!group) continue;
1469
+ let sourcePath = `${group.__meta.relativeContentPath.replace(/\\/g, "/")}/${file.relativePath.replace(/\\/g, "/")}`;
1470
+ sourcePathToPage.set(sourcePath, page);
1393
1471
  }
1394
- let configuredPageIDsByGroup = /* @__PURE__ */ new Map();
1395
- for (let group of Object.values(config.groups))
1396
- configuredPageIDsByGroup.set(
1397
- group.key,
1398
- configuredPageIds(group.tree)
1399
- );
1400
- let treeCache = /* @__PURE__ */ new Map();
1401
- function groupTree(groupKey, stack = /* @__PURE__ */ new Set()) {
1402
- let cached = treeCache.get(groupKey);
1403
- if (cached) return cached;
1404
- if (stack.has(groupKey)) return [];
1472
+ let groupEntries = Object.values(config.groups), childrenByGroup = /* @__PURE__ */ new Map();
1473
+ for (let group of groupEntries) {
1474
+ if (!group.parentKey) continue;
1475
+ let children = childrenByGroup.get(group.parentKey) ?? [];
1476
+ children.push(group.key), childrenByGroup.set(group.parentKey, children);
1477
+ }
1478
+ for (let children of childrenByGroup.values())
1479
+ children.sort((a, b) => a.localeCompare(b));
1480
+ function groupNode(groupKey, presentation, label, fallback, stack, placementPath) {
1481
+ if (stack.has(groupKey))
1482
+ throw navigationError(
1483
+ `Recursive group placement for "${groupKey}".`,
1484
+ groupKey,
1485
+ groupKey
1486
+ );
1487
+ let group = config.groups[groupKey];
1488
+ if (!group)
1489
+ throw navigationError(
1490
+ `Navigation references missing group "${groupKey}".`,
1491
+ groupKey,
1492
+ groupKey
1493
+ );
1494
+ let children = buildGroupItems(
1495
+ groupKey,
1496
+ group.localNavigation ?? fallback ?? group.navigation,
1497
+ /* @__PURE__ */ new Set([...stack, groupKey]),
1498
+ fallback
1499
+ ), route = group.path;
1500
+ return {
1501
+ id: nodeId(groupKey, placementPath, "group"),
1502
+ type: "group",
1503
+ label: label ?? group.title ?? groupKey,
1504
+ groupKey,
1505
+ route,
1506
+ presentation,
1507
+ ...presentation === "page" ? { href: route } : {},
1508
+ children
1509
+ };
1510
+ }
1511
+ function buildGroupItems(groupKey, navigation, stack = /* @__PURE__ */ new Set(), inheritedFallback) {
1405
1512
  let group = config.groups[groupKey];
1406
1513
  if (!group) return [];
1407
- stack.add(groupKey);
1408
- let groupPages = pagesByGroup.get(group.key) ?? [], pagesByFileID = pagesByFileIDByGroup.get(group.key) ?? /* @__PURE__ */ new Map(), routesByFileID = routesByGroup.get(group.key) ?? /* @__PURE__ */ new Map(), configTree = group.tree, configuredPageIDs = configuredPageIDsByGroup.get(group.key), tree2 = configTree ? [
1409
- ...configTree.map(
1410
- (node) => normalizeTreeNode(
1411
- node,
1412
- group.key,
1413
- pagesByFileID,
1414
- routesByFileID,
1415
- {
1416
- childTreeForGroup: (childGroup) => groupTree(childGroup, new Set(stack)),
1417
- config
1418
- }
1419
- )
1420
- ).filter((node) => node !== null),
1421
- ...defaultTree(
1422
- group.key,
1423
- groupPages.filter((page) => !configuredPageIDs?.has(page.fileID)),
1424
- routesByFileID
1514
+ let groupPages = pagesByGroup.get(groupKey) ?? [], routesByFileID = routesByGroup.get(groupKey) ?? /* @__PURE__ */ new Map(), groupContentPath = group?.__meta.relativeContentPath ?? "content", placedPages = /* @__PURE__ */ new Set(), placedGroups = /* @__PURE__ */ new Set();
1515
+ function normalizeEntry(entry, source) {
1516
+ let id = nodeId(groupKey, source, entry.type);
1517
+ if (entry.type === "page") {
1518
+ if (entry.group) {
1519
+ let target = entry.group, targetGroup = config.groups[target];
1520
+ if (!targetGroup)
1521
+ throw navigationError(
1522
+ `Navigation references missing group "${target}".`,
1523
+ groupKey,
1524
+ target
1525
+ );
1526
+ if (targetGroup.parentKey !== groupKey)
1527
+ throw navigationError(
1528
+ `Group "${target}" must be a child of "${groupKey}".`,
1529
+ groupKey,
1530
+ target
1531
+ );
1532
+ if (placedGroups.has(target))
1533
+ throw navigationError(
1534
+ `Group "${target}" appears more than once in navigation.`,
1535
+ groupKey,
1536
+ target
1537
+ );
1538
+ return placedGroups.add(target), groupNode(
1539
+ target,
1540
+ "page",
1541
+ entry.label,
1542
+ entry.navigation ?? inheritedFallback,
1543
+ stack,
1544
+ source
1545
+ );
1546
+ }
1547
+ let sourcePath = entry.file ?? "", page = sourcePathToPage.get(sourcePath), expectedPrefix = `${groupContentPath.replace(/\\/g, "/")}/`;
1548
+ if (!sourcePath.startsWith(expectedPrefix))
1549
+ throw navigationError(
1550
+ `Page reference "${entry.file ?? ""}" does not belong to "${groupKey}".`,
1551
+ groupKey,
1552
+ entry.file ?? ""
1553
+ );
1554
+ if (!page || page.group !== groupKey) return null;
1555
+ if (placedPages.has(page.fileID))
1556
+ throw navigationError(
1557
+ `Page "${entry.file}" appears more than once in navigation.`,
1558
+ groupKey,
1559
+ entry.file ?? ""
1560
+ );
1561
+ placedPages.add(page.fileID);
1562
+ let route = routesByFileID.get(page.fileID);
1563
+ if (!route)
1564
+ throw navigationError(
1565
+ `Page route is missing for "${entry.file}".`,
1566
+ groupKey,
1567
+ entry.file ?? ""
1568
+ );
1569
+ let children = entry.items?.map((child, index) => normalizeEntry(child, `${source}.${index}`)).filter((child) => child !== null);
1570
+ return {
1571
+ id,
1572
+ type: "page",
1573
+ label: entry.label ?? page.frontmatter.title,
1574
+ href: route.path,
1575
+ page: {
1576
+ fileID: page.fileID,
1577
+ group: page.group,
1578
+ path: page.path,
1579
+ frontmatter: page.frontmatter
1580
+ },
1581
+ ...children?.length ? { children } : {}
1582
+ };
1583
+ }
1584
+ if (entry.type === "folder" && entry.group) {
1585
+ let target = entry.group, targetGroup = config.groups[target];
1586
+ if (!targetGroup)
1587
+ throw navigationError(
1588
+ `Navigation references missing group "${target}".`,
1589
+ groupKey,
1590
+ target
1591
+ );
1592
+ if (targetGroup.parentKey !== groupKey)
1593
+ throw navigationError(
1594
+ `Group "${target}" must be a child of "${groupKey}".`,
1595
+ groupKey,
1596
+ target
1597
+ );
1598
+ if (placedGroups.has(target))
1599
+ throw navigationError(
1600
+ `Group "${target}" appears more than once in navigation.`,
1601
+ groupKey,
1602
+ target
1603
+ );
1604
+ return placedGroups.add(target), groupNode(
1605
+ target,
1606
+ "folder",
1607
+ entry.label,
1608
+ entry.navigation ?? inheritedFallback,
1609
+ stack,
1610
+ source
1611
+ );
1612
+ }
1613
+ if (entry.type === "section" || entry.type === "folder") {
1614
+ let children = entry.items?.map((child, index) => normalizeEntry(child, `${source}.${index}`)).filter((child) => child !== null) ?? [];
1615
+ return { id, type: entry.type, label: entry.label, children };
1616
+ }
1617
+ return entry.type === "link" ? { id, type: "link", label: entry.label, href: entry.url } : entry.type === "view" ? {
1618
+ id,
1619
+ type: "view",
1620
+ ...entry.id ? { viewId: entry.id } : {},
1621
+ ...entry.label ? { label: entry.label } : {},
1622
+ items: entry.items.map((child, index) => normalizeEntry(child, `${source}.${index}`)).filter((child) => child !== null)
1623
+ } : {
1624
+ id,
1625
+ type: "switch-view",
1626
+ label: entry.label,
1627
+ target: entry.target
1628
+ };
1629
+ }
1630
+ let items = navigation?.items.map(
1631
+ (entry, index) => normalizeEntry(entry, `navigation.items.${index}`)
1632
+ ).filter((entry) => entry !== null) ?? [], automatic = defaultItems({
1633
+ group: groupKey,
1634
+ pages: groupPages.filter((page) => !placedPages.has(page.fileID)),
1635
+ routes: routesByFileID,
1636
+ childGroups: (childrenByGroup.get(groupKey) ?? []).filter(
1637
+ (key) => !placedGroups.has(key)
1638
+ ),
1639
+ contentPath: groupContentPath,
1640
+ sort: navigation?.sort,
1641
+ sortWarning: navigation?.sortWarning,
1642
+ onWarning: onNavigationWarning,
1643
+ makeGroup: (key, presentation, label) => groupNode(
1644
+ key,
1645
+ presentation,
1646
+ label,
1647
+ inheritedFallback,
1648
+ stack,
1649
+ `default:${key}`
1650
+ )
1651
+ });
1652
+ return items.push(...automatic), items;
1653
+ }
1654
+ let rootKeys = groupEntries.filter((group) => !group.parentKey).map((group) => group.key).sort((a, b) => a.localeCompare(b)), navigationByRoot = /* @__PURE__ */ new Map();
1655
+ for (let rootGroup of rootKeys)
1656
+ navigationByRoot.set(rootGroup, {
1657
+ rootGroup,
1658
+ items: buildGroupItems(
1659
+ rootGroup,
1660
+ config.groups[rootGroup]?.localNavigation ?? config.groups[rootGroup]?.navigation
1425
1661
  )
1426
- ] : defaultTree(group.key, groupPages, routesByFileID);
1427
- return stack.delete(groupKey), treeCache.set(groupKey, tree2), tree2;
1662
+ });
1663
+ for (let [rootGroup, navigation] of navigationByRoot) {
1664
+ let inspect2 = function(nodes, path) {
1665
+ for (let [index, node] of nodes.entries()) {
1666
+ let source = `${path}.${index}`;
1667
+ if (node.type === "view" && node.viewId) {
1668
+ if (views.has(node.viewId))
1669
+ throw navigationError(
1670
+ `View ID "${node.viewId}" is duplicated.`,
1671
+ rootGroup,
1672
+ node.viewId,
1673
+ source
1674
+ );
1675
+ views.set(node.viewId, source);
1676
+ }
1677
+ node.type === "switch-view" && switches.push({ target: node.target, source }), "children" in node && node.children && inspect2(node.children, source), node.type === "view" && inspect2(node.items, source);
1678
+ }
1679
+ };
1680
+ var inspect = inspect2;
1681
+ let views = /* @__PURE__ */ new Map(), switches = [];
1682
+ inspect2(navigation.items, "navigation.items");
1683
+ for (let control of switches) {
1684
+ let viewTarget = typeof control.target == "string" ? void 0 : control.target.view, groupTarget = typeof control.target == "string" ? control.target : void 0;
1685
+ if (viewTarget && !views.has(viewTarget))
1686
+ throw navigationError(
1687
+ `Switch-view target "${viewTarget}" does not exist.`,
1688
+ rootGroup,
1689
+ viewTarget,
1690
+ control.source
1691
+ );
1692
+ if (groupTarget && !config.groups[groupTarget] && !views.has(groupTarget))
1693
+ throw navigationError(
1694
+ `Switch-view target "${groupTarget}" is neither a group nor a named view.`,
1695
+ rootGroup,
1696
+ groupTarget,
1697
+ control.source
1698
+ );
1699
+ }
1428
1700
  }
1429
1701
  let groups = Object.fromEntries(
1430
- Object.values(config.groups).map((group) => {
1431
- let groupPages = pagesByGroup.get(group.key) ?? [], groupData = {
1432
- key: group.key,
1433
- title: group.title ?? group.key,
1434
- path: group.path,
1435
- origin: group.origin ?? (group.key === ROOT_CONTENT_GROUP_KEY ? void 0 : config.origin),
1436
- ancestry: group.ancestry,
1437
- ...group.og ? { og: group.og } : {},
1438
- meta: group.meta,
1439
- tree: groupTree(group.key),
1440
- pages: groupPages
1702
+ groupEntries.map((group) => {
1703
+ let rootGroup = group.ancestry[0] ?? group.key, navigation = navigationByRoot.get(rootGroup) ?? {
1704
+ rootGroup,
1705
+ items: []
1441
1706
  };
1442
- return [group.key, groupData];
1707
+ return [
1708
+ group.key,
1709
+ {
1710
+ key: group.key,
1711
+ title: group.title ?? group.key,
1712
+ path: group.path,
1713
+ origin: group.origin ?? (group.key === ROOT_CONTENT_GROUP_KEY ? void 0 : config.origin),
1714
+ ancestry: group.ancestry,
1715
+ ...group.metadata ? { metadata: group.metadata } : {},
1716
+ meta: group.meta,
1717
+ navigation,
1718
+ pages: pagesByGroup.get(group.key) ?? []
1719
+ }
1720
+ ];
1443
1721
  })
1444
- ), tree = Object.values(groups).filter((group) => !config.groups[group.key]?.parentKey).flatMap(
1445
- (group) => group.key === ROOT_CONTENT_GROUP_KEY ? group.tree : [
1446
- {
1447
- type: "section",
1448
- id: group.key,
1449
- title: group.title,
1450
- path: group.path,
1451
- group: group.key,
1452
- children: group.tree
1453
- }
1454
- ]
1455
1722
  ), manifest = {
1456
1723
  site: content.site,
1457
1724
  groups,
1458
- tree,
1459
1725
  routes: content.routes,
1460
1726
  pages,
1461
1727
  contentHash: ""
@@ -1466,7 +1732,8 @@ function createManifest(config, content) {
1466
1732
  // src/lib/compiler/source/prepare-publish-os-source.ts
1467
1733
  async function preparePublishOsSource({
1468
1734
  publishOsDir,
1469
- sourceIdentity
1735
+ sourceIdentity,
1736
+ includeDrafts = !1
1470
1737
  }) {
1471
1738
  try {
1472
1739
  let sourceRoot = resolve5(publishOsDir), boundary = await assertPublishOsSourceBoundary(
@@ -1475,35 +1742,41 @@ async function preparePublishOsSource({
1475
1742
  ), config = await resolvePublishOsConfig(sourceRoot), content = await scanContent(config), media = await buildMediaAssets({
1476
1743
  config,
1477
1744
  publishOsDir
1478
- }), faviconsAndLogos = await buildFaviconsAndLogos({ publishOsDir }), missingPages = await missingPageReferences({
1745
+ }), faviconsAndLogos = buildFaviconsAndLogos({
1746
+ media
1747
+ }), missingPages = await missingPageReferences({
1479
1748
  sourceRoot,
1480
1749
  config,
1481
1750
  content
1482
- });
1751
+ }), sourceDiagnostics = [
1752
+ ...boundary.nestedSources.map((file) => ({
1753
+ code: "SOURCE_NESTED_EXCLUDED",
1754
+ level: "warning",
1755
+ message: "Nested publish.os source is excluded from this site.",
1756
+ details: { file }
1757
+ })),
1758
+ ...content.warnings,
1759
+ ...missingPages,
1760
+ ...faviconsAndLogos.warnings,
1761
+ ...media.warnings.map((warning2) => ({
1762
+ ...warning2,
1763
+ level: "warning"
1764
+ }))
1765
+ ];
1766
+ if (missingPages.some((diagnostic) => diagnostic.level === "error"))
1767
+ return { diagnostics: sourceDiagnostics };
1768
+ let navigationWarnings = [], manifest = createManifest(
1769
+ config,
1770
+ includeDrafts ? { ...content, routes: content.candidateRoutes } : content,
1771
+ (warning2) => navigationWarnings.push(warning2)
1772
+ );
1483
1773
  return {
1484
- diagnostics: [
1485
- ...boundary.nestedSources.map((file) => ({
1486
- code: "SOURCE_NESTED_EXCLUDED",
1487
- level: "warning",
1488
- message: "Nested publish.os source is excluded from this site.",
1489
- details: { file }
1490
- })),
1491
- ...content.warnings,
1492
- ...missingPages,
1493
- ...media.warnings.map((warning2) => ({
1494
- ...warning2,
1495
- level: "warning"
1496
- })),
1497
- ...faviconsAndLogos.warnings.map((warning2) => ({
1498
- ...warning2,
1499
- level: "warning"
1500
- }))
1501
- ],
1774
+ diagnostics: [...sourceDiagnostics, ...navigationWarnings],
1502
1775
  source: {
1503
1776
  assets: { faviconsAndLogos, media },
1504
1777
  config,
1505
1778
  content,
1506
- manifest: createManifest(config, content)
1779
+ manifest
1507
1780
  }
1508
1781
  };
1509
1782
  } catch (error) {
@@ -1521,12 +1794,12 @@ async function preparePublishOsSource({
1521
1794
 
1522
1795
  // src/lib/compiler/theme/theme-token-diagnostics.ts
1523
1796
  import { readdirSync, readFileSync } from "fs";
1524
- import { extname as extname2, join as join3, resolve as resolve6 } from "path";
1797
+ import { extname as extname3, join as join3, resolve as resolve6 } from "path";
1525
1798
  import postcss from "postcss";
1526
1799
  function cssFiles(directory) {
1527
1800
  return readdirSync(directory, { withFileTypes: !0 }).flatMap((entry) => {
1528
1801
  let path = join3(directory, entry.name);
1529
- return entry.isDirectory() ? cssFiles(path) : extname2(entry.name) === ".css" ? [path] : [];
1802
+ return entry.isDirectory() ? cssFiles(path) : extname3(entry.name) === ".css" ? [path] : [];
1530
1803
  });
1531
1804
  }
1532
1805
  function readDesignTokens() {
@@ -1587,17 +1860,19 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
1587
1860
  "GroupContext",
1588
1861
  "LayoutProps",
1589
1862
  "ManifestLike",
1590
- "NavFolder",
1591
- "NavItem",
1592
- "NavLink",
1593
- "NavPage",
1594
- "NavSection",
1863
+ "NavigationContainerNode",
1864
+ "NavigationGroupNode",
1865
+ "NavigationLinkNode",
1866
+ "NavigationNode",
1867
+ "NavigationPageNode",
1868
+ "NavigationSwitchViewNode",
1869
+ "NavigationViewNode",
1595
1870
  "PageData",
1596
1871
  "PageProps",
1872
+ "ResolvedNavigation",
1597
1873
  "RootLayoutProps",
1598
1874
  "RootPageProps",
1599
- "SiteData",
1600
- "TreeNode"
1875
+ "SiteData"
1601
1876
  ]), ROUTE_EXECUTION_EXPORTS = [
1602
1877
  "runtime",
1603
1878
  "dynamic",
@@ -1647,7 +1922,7 @@ function normalizeLocalFontSourcePath(value) {
1647
1922
  return value.replace(/^\/+/, "").replace(/^\.\//, "");
1648
1923
  }
1649
1924
  function isFontFilePath(path) {
1650
- return FONT_FILE_EXTENSIONS.has(extname3(path).toLowerCase());
1925
+ return FONT_FILE_EXTENSIONS.has(extname4(path).toLowerCase());
1651
1926
  }
1652
1927
  function isValidSourceFontFilePath(path) {
1653
1928
  return path.startsWith("fonts/") && isFontFilePath(path);
@@ -1692,7 +1967,7 @@ async function validateConfiguredLocalFontSources({
1692
1967
  return sourcePaths;
1693
1968
  }
1694
1969
  function isHardRejectedPath(path) {
1695
- let name = basename(path);
1970
+ let name = basename2(path);
1696
1971
  return isReservedFrameworkPath(path) || path.split("/").some((segment) => ["packages", "scripts"].includes(segment)) || LOCKFILES.has(name) || name === "tsconfig.json" && path !== "tsconfig.json";
1697
1972
  }
1698
1973
  function isAllowedBareImport(specifier) {
@@ -1704,6 +1979,10 @@ function isAllowedBareImport(specifier) {
1704
1979
  }
1705
1980
  return ALLOWED_BARE_IMPORTS.has(specifier);
1706
1981
  }
1982
+ function isOriginalDefaultComponentBridge(options) {
1983
+ let sourcePath = originalDefaultComponentSourcePath(options.specifier);
1984
+ return sourcePath ? options.relativeFile.replace(/\.[^.]+$/, "") === sourcePath : !1;
1985
+ }
1707
1986
  function addFileError(store, code, message, file, details = {}) {
1708
1987
  store.error(code, message, { ...details, file });
1709
1988
  }
@@ -1790,7 +2069,7 @@ function importDetails(options) {
1790
2069
  };
1791
2070
  }
1792
2071
  function scriptKindForPath(path) {
1793
- switch (extname3(path).toLowerCase()) {
2072
+ switch (extname4(path).toLowerCase()) {
1794
2073
  case ".tsx":
1795
2074
  return ts2.ScriptKind.TSX;
1796
2075
  case ".jsx":
@@ -2029,7 +2308,7 @@ function validateSourceFilePath({
2029
2308
  groupFolders,
2030
2309
  store
2031
2310
  }) {
2032
- if (basename(file) === "leaf.tsx" && (file === "leaf.tsx" || groupFolders.has(dirname(file))) || file.split("/").includes("(leaf)")) {
2311
+ if (basename2(file) === "leaf.tsx" && (file === "leaf.tsx" || groupFolders.has(dirname(file))) || file.split("/").includes("(leaf)")) {
2033
2312
  let suggestedPath = file.replaceAll("(leaf)", "(page)").replace(/leaf\.tsx$/, "page.tsx");
2034
2313
  return addFileError(
2035
2314
  store,
@@ -2056,7 +2335,7 @@ function validateSourceFontFile({
2056
2335
  return isValidSourceFontFilePath(file) ? (configured || addFileWarning(
2057
2336
  store,
2058
2337
  "FRAMEWORK_FONT_FILE_UNUSED",
2059
- `Font file "${basename(file)}" is not used by the current font configuration and will not be included in the site.`,
2338
+ `Font file "${basename2(file)}" is not used by the current font configuration and will not be included in the site.`,
2060
2339
  file,
2061
2340
  { fontSourcePath: `/${file}`, suggestion: fontConfigHint(file) }
2062
2341
  ), !0) : (addFileWarning(
@@ -2066,7 +2345,7 @@ function validateSourceFontFile({
2066
2345
  file,
2067
2346
  {
2068
2347
  fontSourcePath: `/${file}`,
2069
- suggestion: fontConfigHint(`fonts/${basename(file)}`)
2348
+ suggestion: fontConfigHint(`fonts/${basename2(file)}`)
2070
2349
  }
2071
2350
  ), !0);
2072
2351
  }
@@ -2075,7 +2354,7 @@ function validateLegacyIconReferences(source, file, store) {
2075
2354
  match && addFileError(
2076
2355
  store,
2077
2356
  "FRAMEWORK_ICON_RESOLVER_REJECTED",
2078
- 'The Icon resolver is removed. Import an exact component from "@phosphor-icons/react/ssr" in a custom MDX component instead.',
2357
+ 'The Icon resolver is removed. Import an exact named component from "@phosphor-icons/react/ssr" in MDX or TSX instead.',
2079
2358
  file,
2080
2359
  sourceDetails(source, match.index, match[0] ?? "")
2081
2360
  );
@@ -2107,7 +2386,7 @@ async function validateSourceFileContents({
2107
2386
  checkedCss,
2108
2387
  store
2109
2388
  }) {
2110
- let extension = extname3(file), sourceModule = SOURCE_EXTENSIONS.has(extension), validatesAttribution = ATTRIBUTION_VALIDATION_EXTENSIONS.has(extension), scansLegacyIcons = sourceModule || MARKDOWN_ICON_SCAN_EXTENSIONS.has(extension) && !file.startsWith("assets/");
2389
+ let extension = extname4(file), sourceModule = SOURCE_EXTENSIONS.has(extension), validatesAttribution = ATTRIBUTION_VALIDATION_EXTENSIONS.has(extension), scansLegacyIcons = sourceModule || MARKDOWN_ICON_SCAN_EXTENSIONS.has(extension) && !file.startsWith("assets/");
2111
2390
  if (!validatesAttribution && !scansLegacyIcons) return;
2112
2391
  let absolutePath = resolve7(publishOsDir, file), source = await readFile5(absolutePath, "utf-8");
2113
2392
  validatesAttribution && validateAttributionReferences(source, file, store), extension === ".css" && (validateThemeTokenSpelling(source, file, store), await validateCssDependencies({
@@ -2115,7 +2394,23 @@ async function validateSourceFileContents({
2115
2394
  filePath: absolutePath,
2116
2395
  css: source,
2117
2396
  checked: checkedCss
2118
- })), scansLegacyIcons && validateLegacyIconReferences(source, file, store), sourceModule && (validateSourceModuleSafety(source, file, store), await validateSourceModule({
2397
+ }));
2398
+ let importsIconBinding = !1;
2399
+ if (extension === ".mdx" && !file.startsWith("assets/"))
2400
+ try {
2401
+ importsIconBinding = (await validateMdxIconImports(source)).some(({ local }) => local === "Icon");
2402
+ } catch (error) {
2403
+ let line = error instanceof MdxIconImportError ? error.line : 1, column = error instanceof MdxIconImportError ? error.column : 1;
2404
+ addFileError(
2405
+ store,
2406
+ error instanceof MdxIconImportError ? error.code : "FRAMEWORK_MDX_IMPORT_INVALID",
2407
+ error instanceof Error ? error.message : String(error),
2408
+ file,
2409
+ { line, column, sourceLine: source.split(`
2410
+ `)[line - 1] ?? "" }
2411
+ );
2412
+ }
2413
+ scansLegacyIcons && !importsIconBinding && validateLegacyIconReferences(source, file, store), sourceModule && (validateSourceModuleSafety(source, file, store), await validateSourceModule({
2119
2414
  absolutePath,
2120
2415
  groupFolders,
2121
2416
  publishOsDir,
@@ -2140,18 +2435,32 @@ async function validateSourceBoundary(publishOsDir, config, store) {
2140
2435
  "package.json"
2141
2436
  );
2142
2437
  for (let file of await walkAllFiles(publishOsDir))
2143
- validateSourceFilePath({ file, groupFolders, store }) && (validateSourceFontFile({ file, configuredFontSourcePaths, store }) || (isApprovedPublishOsPath(file, groupFolders) || addFileWarning(
2144
- store,
2145
- "FRAMEWORK_SOURCE_PATH_IGNORED",
2146
- "This file is outside the supported publish.os structure and will not be included in the site.",
2147
- file
2148
- ), await validateSourceFileContents({
2149
- publishOsDir,
2150
- file,
2151
- groupFolders,
2152
- checkedCss,
2153
- store
2154
- })));
2438
+ if (validateSourceFilePath({ file, groupFolders, store }) && !validateSourceFontFile({ file, configuredFontSourcePaths, store })) {
2439
+ if (!isApprovedPublishOsPath(file, groupFolders))
2440
+ if (file.startsWith("public/")) {
2441
+ let assetPath = file.slice(7);
2442
+ addFileWarning(
2443
+ store,
2444
+ "FRAMEWORK_SOURCE_PUBLIC_UNSUPPORTED",
2445
+ `Files in publish.os/public are not included. Move this file to publish.os/assets/${assetPath} and reference it as /assets/${assetPath}.`,
2446
+ file,
2447
+ { correction: `assets/${assetPath}` }
2448
+ );
2449
+ } else
2450
+ addFileWarning(
2451
+ store,
2452
+ "FRAMEWORK_SOURCE_PATH_IGNORED",
2453
+ "This file is outside the supported publish.os structure and will not be included in the site.",
2454
+ file
2455
+ );
2456
+ await validateSourceFileContents({
2457
+ publishOsDir,
2458
+ file,
2459
+ groupFolders,
2460
+ checkedCss,
2461
+ store
2462
+ });
2463
+ }
2155
2464
  }
2156
2465
  async function validateCssDependencies({
2157
2466
  publishOsDir,
@@ -2296,7 +2605,7 @@ async function validateImportSpecifier(options) {
2296
2605
  );
2297
2606
  return;
2298
2607
  }
2299
- isAllowedBareImport(options.specifier) || addFileError(
2608
+ !isAllowedBareImport(options.specifier) && !isOriginalDefaultComponentBridge(options) && addFileError(
2300
2609
  options.store,
2301
2610
  "FRAMEWORK_IMPORT_NOT_ALLOWED",
2302
2611
  `Can't import from "${options.specifier}". Source modules can only import approved runtime packages.`,
@@ -2304,10 +2613,18 @@ async function validateImportSpecifier(options) {
2304
2613
  importDetails(options)
2305
2614
  );
2306
2615
  }
2307
- function toValidationDiagnostic(diagnostic) {
2616
+ function toValidationDiagnostic(diagnostic, sourceRoot) {
2617
+ let path = diagnostic.details?.file ?? diagnostic.details?.path ?? diagnostic.file, file = typeof path == "string" && isSameOrDescendant(sourceRoot, resolve7(sourceRoot, path)) ? relative4(sourceRoot, resolve7(sourceRoot, path)).split(sep).join("/") : void 0;
2308
2618
  return {
2309
2619
  ...diagnostic,
2310
- file: typeof diagnostic.details?.file == "string" ? diagnostic.details.file : diagnostic.file,
2620
+ message: sourceRoot === parse(sourceRoot).root ? diagnostic.message : diagnostic.message.replaceAll(`${sourceRoot}${sep}`, ""),
2621
+ file,
2622
+ ...typeof diagnostic.details?.field == "string" && !diagnostic.details.propertyPath ? {
2623
+ details: {
2624
+ ...diagnostic.details,
2625
+ propertyPath: diagnostic.details.field
2626
+ }
2627
+ } : {},
2311
2628
  line: typeof diagnostic.details?.line == "number" ? diagnostic.details.line : diagnostic.line,
2312
2629
  column: typeof diagnostic.details?.column == "number" ? diagnostic.details.column : diagnostic.column
2313
2630
  };
@@ -2322,7 +2639,8 @@ async function validatePublishOsSource(options) {
2322
2639
  try {
2323
2640
  let prepared = options.sourcePreparation ?? await preparePublishOsSource({
2324
2641
  publishOsDir,
2325
- sourceIdentity: options.sourceIdentity
2642
+ sourceIdentity: options.sourceIdentity,
2643
+ includeDrafts: options.includeDrafts
2326
2644
  });
2327
2645
  for (let diagnostic of prepared.diagnostics)
2328
2646
  store.add(diagnostic);
@@ -2330,7 +2648,7 @@ async function validatePublishOsSource(options) {
2330
2648
  } catch (error) {
2331
2649
  error instanceof AppError ? store.error(error.code, error.message, error.details) : store.error("FRAMEWORK_VALIDATE_UNKNOWN", String(error));
2332
2650
  }
2333
- let allDiagnostics = store.all().map(toValidationDiagnostic), errors = allDiagnostics.filter((d) => d.level === "error"), warnings = allDiagnostics.filter((d) => d.level === "warning"), valid = errors.length === 0;
2651
+ let allDiagnostics = store.all().map((diagnostic) => toValidationDiagnostic(diagnostic, publishOsDir)), errors = allDiagnostics.filter((d) => d.level === "error"), warnings = allDiagnostics.filter((d) => d.level === "warning"), valid = errors.length === 0;
2334
2652
  return {
2335
2653
  valid,
2336
2654
  errors,