emdash 0.25.1 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/dist/api/route-utils.d.mts +3 -3
  2. package/dist/api/route-utils.mjs +11 -11
  3. package/dist/{api-D2_sbavn.mjs → api-BMfveMoB.mjs} +15 -15
  4. package/dist/{api-D2_sbavn.mjs.map → api-BMfveMoB.mjs.map} +1 -1
  5. package/dist/{apply-Bk2mAuLn.mjs → apply--IVNSdXn.mjs} +18 -16
  6. package/dist/apply--IVNSdXn.mjs.map +1 -0
  7. package/dist/astro/index.d.mts +7 -7
  8. package/dist/astro/index.mjs +2 -1
  9. package/dist/astro/index.mjs.map +1 -1
  10. package/dist/astro/middleware/auth.d.mts +7 -7
  11. package/dist/astro/middleware/auth.mjs +2 -2
  12. package/dist/astro/middleware/redirect.mjs +4 -4
  13. package/dist/astro/middleware.d.mts +1 -1
  14. package/dist/astro/middleware.mjs +85 -85
  15. package/dist/astro/middleware.mjs.map +1 -1
  16. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +3 -3
  17. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +3 -3
  18. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  19. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  20. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  21. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +4 -4
  22. package/dist/astro/routes/api/admin/byline-fields/index.mjs +4 -4
  23. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +4 -4
  24. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +10 -10
  25. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +10 -10
  26. package/dist/astro/routes/api/admin/bylines/index.mjs +10 -10
  27. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +8 -8
  28. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  29. package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -7
  30. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  31. package/dist/astro/routes/api/admin/comments/index.mjs +7 -7
  32. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  33. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  34. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  35. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  36. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +28 -28
  37. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +28 -28
  38. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +28 -28
  39. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -28
  40. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -28
  41. package/dist/astro/routes/api/admin/plugins/index.mjs +28 -28
  42. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  43. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -28
  44. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -28
  45. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -28
  46. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -28
  47. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -29
  48. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -28
  49. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -29
  50. package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -28
  51. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -28
  52. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  53. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -28
  54. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  55. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  56. package/dist/astro/routes/api/admin/users/_id_/index.mjs +3 -3
  57. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  58. package/dist/astro/routes/api/admin/users/index.mjs +3 -3
  59. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  60. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  61. package/dist/astro/routes/api/auth/invite/complete.mjs +3 -3
  62. package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
  63. package/dist/astro/routes/api/auth/invite/register-options.mjs +3 -3
  64. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  65. package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
  66. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  67. package/dist/astro/routes/api/auth/me.mjs +4 -4
  68. package/dist/astro/routes/api/auth/passkey/_id_.mjs +3 -3
  69. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  70. package/dist/astro/routes/api/auth/passkey/options.mjs +4 -4
  71. package/dist/astro/routes/api/auth/passkey/register/options.mjs +3 -3
  72. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +3 -3
  73. package/dist/astro/routes/api/auth/passkey/verify.mjs +3 -3
  74. package/dist/astro/routes/api/auth/signup/complete.mjs +3 -3
  75. package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
  76. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  77. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +7 -7
  78. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +7 -7
  79. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  80. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  81. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  82. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  83. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +4 -4
  84. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +4 -4
  85. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  86. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +4 -4
  88. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +9 -9
  89. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  91. package/dist/astro/routes/api/content/_collection_/_id_.mjs +4 -4
  92. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  93. package/dist/astro/routes/api/content/_collection_/index.mjs +4 -4
  94. package/dist/astro/routes/api/content/_collection_/trash.mjs +4 -4
  95. package/dist/astro/routes/api/dashboard.mjs +8 -8
  96. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  97. package/dist/astro/routes/api/import/probe.d.mts +2 -2
  98. package/dist/astro/routes/api/import/probe.mjs +4 -4
  99. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  100. package/dist/astro/routes/api/import/wordpress/execute.d.mts +7 -7
  101. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  102. package/dist/astro/routes/api/import/wordpress/media.d.mts +7 -7
  103. package/dist/astro/routes/api/import/wordpress/media.mjs +4 -4
  104. package/dist/astro/routes/api/import/wordpress/prepare.mjs +6 -6
  105. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +5 -5
  106. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +4 -4
  107. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +7 -7
  108. package/dist/astro/routes/api/manifest.mjs +3 -3
  109. package/dist/astro/routes/api/mcp.mjs +34 -34
  110. package/dist/astro/routes/api/media/_id_/confirm.mjs +4 -4
  111. package/dist/astro/routes/api/media/_id_.mjs +4 -4
  112. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  113. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  114. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  115. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  116. package/dist/astro/routes/api/media/upload-url.mjs +4 -4
  117. package/dist/astro/routes/api/media.mjs +5 -5
  118. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -6
  119. package/dist/astro/routes/api/menus/_name_/items.mjs +6 -6
  120. package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -6
  121. package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -6
  122. package/dist/astro/routes/api/menus/_name_.mjs +6 -6
  123. package/dist/astro/routes/api/menus/index.mjs +6 -6
  124. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  125. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  126. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  127. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  128. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  129. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  130. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  131. package/dist/astro/routes/api/openapi.json.mjs +2 -2
  132. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  133. package/dist/astro/routes/api/redirects/404s/index.mjs +6 -6
  134. package/dist/astro/routes/api/redirects/404s/summary.mjs +6 -6
  135. package/dist/astro/routes/api/redirects/_id_.mjs +7 -7
  136. package/dist/astro/routes/api/redirects/index.mjs +7 -7
  137. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  138. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  139. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -28
  140. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +28 -28
  141. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +28 -28
  142. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -28
  143. package/dist/astro/routes/api/schema/collections/index.mjs +28 -28
  144. package/dist/astro/routes/api/schema/index.mjs +6 -6
  145. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -28
  146. package/dist/astro/routes/api/schema/orphans/index.mjs +28 -28
  147. package/dist/astro/routes/api/search/enable.mjs +7 -7
  148. package/dist/astro/routes/api/search/index.mjs +6 -6
  149. package/dist/astro/routes/api/search/rebuild.mjs +7 -7
  150. package/dist/astro/routes/api/search/stats.mjs +6 -6
  151. package/dist/astro/routes/api/search/suggest.mjs +6 -6
  152. package/dist/astro/routes/api/sections/_slug_.mjs +6 -6
  153. package/dist/astro/routes/api/sections/index.mjs +6 -6
  154. package/dist/astro/routes/api/settings/email.mjs +4 -4
  155. package/dist/astro/routes/api/settings.mjs +9 -9
  156. package/dist/astro/routes/api/setup/admin-verify.mjs +3 -3
  157. package/dist/astro/routes/api/setup/admin.mjs +3 -3
  158. package/dist/astro/routes/api/setup/dev-bypass.mjs +17 -17
  159. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  160. package/dist/astro/routes/api/setup/index.mjs +18 -18
  161. package/dist/astro/routes/api/setup/status.mjs +3 -3
  162. package/dist/astro/routes/api/snapshot.mjs +3 -3
  163. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +9 -9
  164. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +9 -9
  165. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +9 -9
  166. package/dist/astro/routes/api/taxonomies/index.mjs +9 -9
  167. package/dist/astro/routes/api/themes/preview.mjs +3 -3
  168. package/dist/astro/routes/api/typegen.mjs +5 -5
  169. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +4 -4
  170. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -6
  171. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -6
  172. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  173. package/dist/astro/routes/api/widget-areas/index.mjs +6 -6
  174. package/dist/astro/routes/api/widget-components.mjs +2 -2
  175. package/dist/astro/routes/robots.txt.mjs +5 -5
  176. package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -9
  177. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  178. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  179. package/dist/astro/types.d.mts +7 -7
  180. package/dist/{authorize-CuxZ0i2t.mjs → authorize-BZnOGJFo.mjs} +2 -2
  181. package/dist/{authorize-CuxZ0i2t.mjs.map → authorize-BZnOGJFo.mjs.map} +1 -1
  182. package/dist/{byline-C-YajfV7.mjs → byline-LL8lyqdb.mjs} +6 -6
  183. package/dist/{byline-C-YajfV7.mjs.map → byline-LL8lyqdb.mjs.map} +1 -1
  184. package/dist/{bylines-D4KADDAi.mjs → bylines-BmJ7Vg7J.mjs} +4 -4
  185. package/dist/{bylines-D4KADDAi.mjs.map → bylines-BmJ7Vg7J.mjs.map} +1 -1
  186. package/dist/{bylines-CPc_q2Vd.mjs → bylines-DE47vP8d.mjs} +5 -5
  187. package/dist/{bylines-CPc_q2Vd.mjs.map → bylines-DE47vP8d.mjs.map} +1 -1
  188. package/dist/{cache-C35VWD5D.mjs → cache-D9jF5bYw.mjs} +2 -2
  189. package/dist/{cache-C35VWD5D.mjs.map → cache-D9jF5bYw.mjs.map} +1 -1
  190. package/dist/{chunks-BHgcHh2C.mjs → chunks-OCG163rN.mjs} +2 -2
  191. package/dist/{chunks-BHgcHh2C.mjs.map → chunks-OCG163rN.mjs.map} +1 -1
  192. package/dist/cli/index.mjs +16 -16
  193. package/dist/{comment-BDx50EX5.mjs → comment-D9BLDpZI.mjs} +3 -3
  194. package/dist/{comment-BDx50EX5.mjs.map → comment-D9BLDpZI.mjs.map} +1 -1
  195. package/dist/{comment-reaction-Gv_pgfI3.mjs → comment-reaction-Drl9SkNu.mjs} +2 -2
  196. package/dist/{comment-reaction-Gv_pgfI3.mjs.map → comment-reaction-Drl9SkNu.mjs.map} +1 -1
  197. package/dist/{comments-CXHuQ6jI.mjs → comments-BjJ9SKGU.mjs} +3 -3
  198. package/dist/{comments-CXHuQ6jI.mjs.map → comments-BjJ9SKGU.mjs.map} +1 -1
  199. package/dist/{content-B43-YrIq.mjs → content-ncUWWjna.mjs} +4 -4
  200. package/dist/{content-B43-YrIq.mjs.map → content-ncUWWjna.mjs.map} +1 -1
  201. package/dist/{context-BW3N7wyt.mjs → context-C_g-iJVA.mjs} +7 -7
  202. package/dist/{context-BW3N7wyt.mjs.map → context-C_g-iJVA.mjs.map} +1 -1
  203. package/dist/{dashboard-FIe0S512.mjs → dashboard-CotyqQrW.mjs} +4 -4
  204. package/dist/{dashboard-FIe0S512.mjs.map → dashboard-CotyqQrW.mjs.map} +1 -1
  205. package/dist/db/index.d.mts +2 -2
  206. package/dist/db/index.mjs +1 -1
  207. package/dist/{error-DXnK2wF4.mjs → error-rVGiqWDt.mjs} +2 -2
  208. package/dist/{error-DXnK2wF4.mjs.map → error-rVGiqWDt.mjs.map} +1 -1
  209. package/dist/{field-defs-cache-CYYx8xlG.mjs → field-defs-cache-ljXzJCfc.mjs} +2 -2
  210. package/dist/{field-defs-cache-CYYx8xlG.mjs.map → field-defs-cache-ljXzJCfc.mjs.map} +1 -1
  211. package/dist/{fts-manager-DW_NR-Fs.mjs → fts-manager-BbCCbEKA.mjs} +2 -2
  212. package/dist/{fts-manager-DW_NR-Fs.mjs.map → fts-manager-BbCCbEKA.mjs.map} +1 -1
  213. package/dist/{index-Cz6_JBL3.d.mts → index-BRNabKrx.d.mts} +36 -11
  214. package/dist/index-BRNabKrx.d.mts.map +1 -0
  215. package/dist/{index-qbL02uhs.d.mts → index-Dt0OKJB4.d.mts} +2 -2
  216. package/dist/{index-qbL02uhs.d.mts.map → index-Dt0OKJB4.d.mts.map} +1 -1
  217. package/dist/index.d.mts +8 -8
  218. package/dist/index.mjs +38 -38
  219. package/dist/{load-DMFi4n1p.mjs → load-DMjXghbZ.mjs} +2 -2
  220. package/dist/{load-DMFi4n1p.mjs.map → load-DMjXghbZ.mjs.map} +1 -1
  221. package/dist/{loader-DrpofRon.mjs → loader-BA_zhkS_.mjs} +81 -18
  222. package/dist/loader-BA_zhkS_.mjs.map +1 -0
  223. package/dist/{manifest-schema-BOfPXse0.mjs → manifest-schema-kPGX7VS-.mjs} +3 -1
  224. package/dist/manifest-schema-kPGX7VS-.mjs.map +1 -0
  225. package/dist/media/local-runtime.d.mts +7 -7
  226. package/dist/media/local-runtime.mjs +5 -5
  227. package/dist/{media-BjKxjzk8.mjs → media-C5t_eCnk.mjs} +2 -2
  228. package/dist/{media-BjKxjzk8.mjs.map → media-C5t_eCnk.mjs.map} +1 -1
  229. package/dist/{menus-8O6xNcYW.mjs → menus-CBb6UrMU.mjs} +3 -3
  230. package/dist/{menus-8O6xNcYW.mjs.map → menus-CBb6UrMU.mjs.map} +1 -1
  231. package/dist/{menus-CxqJ-z-N.mjs → menus-CYH09a41.mjs} +50 -68
  232. package/dist/menus-CYH09a41.mjs.map +1 -0
  233. package/dist/{object-cache-SEb2IM4Z.mjs → object-cache-CHbHv83-.mjs} +8 -4
  234. package/dist/object-cache-CHbHv83-.mjs.map +1 -0
  235. package/dist/{options-BvClpP5Q.d.mts → options-41nCWqi9.d.mts} +2 -2
  236. package/dist/{options-BvClpP5Q.d.mts.map → options-41nCWqi9.d.mts.map} +1 -1
  237. package/dist/page/index.d.mts +5 -2
  238. package/dist/page/index.d.mts.map +1 -1
  239. package/dist/page/index.mjs +3 -2
  240. package/dist/page/index.mjs.map +1 -1
  241. package/dist/{parse-DqMWHQ8x.mjs → parse-BwAdS7FZ.mjs} +2 -2
  242. package/dist/{parse-DqMWHQ8x.mjs.map → parse-BwAdS7FZ.mjs.map} +1 -1
  243. package/dist/plugin-types.d.mts +4 -2
  244. package/dist/plugin-types.d.mts.map +1 -1
  245. package/dist/plugin-utils.d.mts +7 -7
  246. package/dist/plugins/adapt-sandbox-entry.d.mts +7 -7
  247. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  248. package/dist/{query-34pfm02Q.mjs → query-BpQb-blG.mjs} +14 -14
  249. package/dist/{query-34pfm02Q.mjs.map → query-BpQb-blG.mjs.map} +1 -1
  250. package/dist/{rate-limit-CBQK4ygJ.mjs → rate-limit-Dap6HMNM.mjs} +2 -2
  251. package/dist/{rate-limit-CBQK4ygJ.mjs.map → rate-limit-Dap6HMNM.mjs.map} +1 -1
  252. package/dist/{redirect-C1KVGYWe.mjs → redirect-_M0trWoo.mjs} +2 -2
  253. package/dist/{redirect-C1KVGYWe.mjs.map → redirect-_M0trWoo.mjs.map} +1 -1
  254. package/dist/{redirects-DgnypVir.mjs → redirects-w0V9vj_j.mjs} +3 -3
  255. package/dist/{redirects-DgnypVir.mjs.map → redirects-w0V9vj_j.mjs.map} +1 -1
  256. package/dist/{registry-3aOAS4My.mjs → registry-g8yYt4cC.mjs} +4 -4
  257. package/dist/{registry-3aOAS4My.mjs.map → registry-g8yYt4cC.mjs.map} +1 -1
  258. package/dist/{runner-CEtq-6Sm.d.mts → runner-BbR3DfrL.d.mts} +2 -2
  259. package/dist/{runner-CEtq-6Sm.d.mts.map → runner-BbR3DfrL.d.mts.map} +1 -1
  260. package/dist/{runner-BsquExlK.mjs → runner-DPdt_Wpa.mjs} +250 -178
  261. package/dist/runner-DPdt_Wpa.mjs.map +1 -0
  262. package/dist/runtime.d.mts +7 -7
  263. package/dist/runtime.mjs +2 -2
  264. package/dist/{schema-BGwx8AQY.mjs → schema-DoLd3j2e.mjs} +5 -5
  265. package/dist/{schema-BGwx8AQY.mjs.map → schema-DoLd3j2e.mjs.map} +1 -1
  266. package/dist/{search-Gl747zMP.mjs → search-BR0ZeZmh.mjs} +4 -4
  267. package/dist/{search-Gl747zMP.mjs.map → search-BR0ZeZmh.mjs.map} +1 -1
  268. package/dist/{sections-C-umuuLj.mjs → sections-5J2YJPqJ.mjs} +3 -3
  269. package/dist/{sections-C-umuuLj.mjs.map → sections-5J2YJPqJ.mjs.map} +1 -1
  270. package/dist/seed/index.d.mts +2 -2
  271. package/dist/seed/index.mjs +15 -15
  272. package/dist/{seo-BNzkiK_W.mjs → seo-DKeXHkzE.mjs} +3 -3
  273. package/dist/{seo-BNzkiK_W.mjs.map → seo-DKeXHkzE.mjs.map} +1 -1
  274. package/dist/{service-CQHK_10l.mjs → service-BbO2s8gT.mjs} +2 -2
  275. package/dist/{service-CQHK_10l.mjs.map → service-BbO2s8gT.mjs.map} +1 -1
  276. package/dist/{settings-fPSR6dJa.mjs → settings-BMNr9Dmi.mjs} +3 -3
  277. package/dist/{settings-fPSR6dJa.mjs.map → settings-BMNr9Dmi.mjs.map} +1 -1
  278. package/dist/{settings-C4tXRYiG.mjs → settings-DJeki-97.mjs} +4 -4
  279. package/dist/{settings-C4tXRYiG.mjs.map → settings-DJeki-97.mjs.map} +1 -1
  280. package/dist/{taxonomies-DbAtL6m4.mjs → taxonomies-CM_fUqLU.mjs} +5 -4
  281. package/dist/taxonomies-CM_fUqLU.mjs.map +1 -0
  282. package/dist/{taxonomies-n8w72lLQ.mjs → taxonomies-D13i2ctt.mjs} +75 -20
  283. package/dist/taxonomies-D13i2ctt.mjs.map +1 -0
  284. package/dist/{taxonomy-BBUlGug5.mjs → taxonomy-BU0LjdVb.mjs} +4 -4
  285. package/dist/{taxonomy-BBUlGug5.mjs.map → taxonomy-BU0LjdVb.mjs.map} +1 -1
  286. package/dist/{types-h2Cwbzi2.d.mts → types-BYnDBVwe.d.mts} +24 -5
  287. package/dist/{types-h2Cwbzi2.d.mts.map → types-BYnDBVwe.d.mts.map} +1 -1
  288. package/dist/{types-CmI1OyQg.mjs → types-CZgK3EMW.mjs} +2 -2
  289. package/dist/{types-CmI1OyQg.mjs.map → types-CZgK3EMW.mjs.map} +1 -1
  290. package/dist/{types-B-gIxFCE.d.mts → types-xIfVRNLp.d.mts} +38 -1
  291. package/dist/types-xIfVRNLp.d.mts.map +1 -0
  292. package/dist/{user-JAuc8VUf.mjs → user-DkG2jKCp.mjs} +3 -3
  293. package/dist/{user-JAuc8VUf.mjs.map → user-DkG2jKCp.mjs.map} +1 -1
  294. package/dist/{validate-Va_An3qb.d.mts → validate-CG0V0wNR.d.mts} +2 -2
  295. package/dist/{validate-Va_An3qb.d.mts.map → validate-CG0V0wNR.d.mts.map} +1 -1
  296. package/dist/{validate-BzXHWnkV.mjs → validate-CuJ1Ps9j.mjs} +2 -2
  297. package/dist/{validate-BzXHWnkV.mjs.map → validate-CuJ1Ps9j.mjs.map} +1 -1
  298. package/dist/{validation-B6DKPTtU.mjs → validation-CwUfTdGF.mjs} +5 -5
  299. package/dist/{validation-B6DKPTtU.mjs.map → validation-CwUfTdGF.mjs.map} +1 -1
  300. package/dist/version-BoNBItP_.mjs +7 -0
  301. package/dist/{version-DHdCA24u.mjs.map → version-BoNBItP_.mjs.map} +1 -1
  302. package/dist/{widgets-C2RbFN0G.mjs → widgets-BfOZ-ajY.mjs} +2 -2
  303. package/dist/{widgets-C2RbFN0G.mjs.map → widgets-BfOZ-ajY.mjs.map} +1 -1
  304. package/dist/{zod-generator-xEHODMJf.mjs → zod-generator-gFsRuNNy.mjs} +6 -3
  305. package/dist/zod-generator-gFsRuNNy.mjs.map +1 -0
  306. package/package.json +5 -5
  307. package/src/api/handlers/taxonomies.ts +5 -1
  308. package/src/astro/integration/virtual-modules.ts +15 -0
  309. package/src/astro/routes/sitemap-[collection].xml.ts +5 -4
  310. package/src/components/EmDashHead.astro +23 -3
  311. package/src/components/JsonLdScript.astro +9 -0
  312. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +16 -0
  313. package/src/database/migrations/046_media_usage_index.ts +115 -0
  314. package/src/database/migrations/047_restore_taxonomy_parent_index.ts +29 -0
  315. package/src/database/migrations/runner.ts +4 -0
  316. package/src/database/repositories/media-usage.ts +444 -0
  317. package/src/database/types.ts +39 -0
  318. package/src/emdash-runtime.ts +61 -52
  319. package/src/index.ts +3 -0
  320. package/src/loader.ts +110 -31
  321. package/src/media/usage/extractor.ts +302 -0
  322. package/src/media/usage/types.ts +46 -0
  323. package/src/object-cache/index.ts +8 -2
  324. package/src/page/metadata.ts +23 -3
  325. package/src/plugin-types.ts +10 -0
  326. package/src/plugins/define-plugin.ts +14 -5
  327. package/src/plugins/hooks.ts +53 -65
  328. package/src/plugins/index.ts +5 -0
  329. package/src/plugins/manager.ts +22 -0
  330. package/src/plugins/manifest-schema.ts +2 -0
  331. package/src/plugins/types.ts +35 -3
  332. package/src/schema/zod-generator.ts +9 -1
  333. package/src/seed/apply.ts +6 -0
  334. package/src/taxonomies/index.ts +121 -18
  335. package/dist/apply-Bk2mAuLn.mjs.map +0 -1
  336. package/dist/index-Cz6_JBL3.d.mts.map +0 -1
  337. package/dist/loader-DrpofRon.mjs.map +0 -1
  338. package/dist/manifest-schema-BOfPXse0.mjs.map +0 -1
  339. package/dist/menus-CxqJ-z-N.mjs.map +0 -1
  340. package/dist/object-cache-SEb2IM4Z.mjs.map +0 -1
  341. package/dist/runner-BsquExlK.mjs.map +0 -1
  342. package/dist/taxonomies-DbAtL6m4.mjs.map +0 -1
  343. package/dist/taxonomies-n8w72lLQ.mjs.map +0 -1
  344. package/dist/types-B-gIxFCE.d.mts.map +0 -1
  345. package/dist/version-DHdCA24u.mjs +0 -7
  346. package/dist/zod-generator-xEHODMJf.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"validate-BzXHWnkV.mjs","names":[],"sources":["../src/seed/validate.ts"],"sourcesContent":["/**\n * Seed file validation\n *\n * Validates a seed file structure before applying it.\n */\n\nimport { FIELD_TYPES } from \"../schema/types.js\";\nimport type { SeedFile, SeedMenuItem, ValidationResult } from \"./types.js\";\n\nconst COLLECTION_FIELD_SLUG_PATTERN = /^[a-z][a-z0-9_]*$/;\nconst SLUG_PATTERN = /^[a-z0-9-]+$/;\nconst REDIRECT_TYPES = new Set([301, 302, 307, 308]);\nconst CRLF_PATTERN = /[\\r\\n]/;\n\n/** Type guard for Record<string, unknown> */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isValidRedirectPath(path: string): boolean {\n\tif (!path.startsWith(\"/\") || path.startsWith(\"//\") || CRLF_PATTERN.test(path)) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\treturn !decodeURIComponent(path).split(\"/\").includes(\"..\");\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Validate a seed file\n *\n * @param data - Unknown data to validate as a seed file\n * @returns Validation result with errors and warnings\n */\nexport function validateSeed(data: unknown): ValidationResult {\n\tconst errors: string[] = [];\n\tconst warnings: string[] = [];\n\n\t// Basic type check\n\tif (!data || typeof data !== \"object\") {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terrors: [\"Seed must be an object\"],\n\t\t\twarnings: [],\n\t\t};\n\t}\n\n\tconst seed = data as Partial<SeedFile>;\n\n\t// Required fields\n\tif (!seed.version) {\n\t\terrors.push(\"Seed must have a version field\");\n\t} else if (seed.version !== \"1\") {\n\t\terrors.push(`Unsupported seed version: ${String(seed.version)}`);\n\t}\n\n\t// defaultLocale backfills the locale of rows that omit one, so a blank or\n\t// whitespace-padded value would silently write empty/invalid locales to the DB.\n\t// Exported seeds never hit this, but it's part of the public schema now.\n\tif (seed.defaultLocale !== undefined) {\n\t\tif (\n\t\t\ttypeof seed.defaultLocale !== \"string\" ||\n\t\t\tseed.defaultLocale.length === 0 ||\n\t\t\tseed.defaultLocale !== seed.defaultLocale.trim()\n\t\t) {\n\t\t\terrors.push(\n\t\t\t\t\"defaultLocale: must be a non-empty string with no leading or trailing whitespace\",\n\t\t\t);\n\t\t}\n\t}\n\n\t// Validate collections\n\tif (seed.collections) {\n\t\tif (!Array.isArray(seed.collections)) {\n\t\t\terrors.push(\"collections must be an array\");\n\t\t} else {\n\t\t\tconst collectionSlugs = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.collections.length; i++) {\n\t\t\t\tconst collection = seed.collections[i];\n\t\t\t\tconst prefix = `collections[${i}]`;\n\n\t\t\t\tif (!collection.slug) {\n\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Check for valid slug format\n\t\t\t\t\tif (!COLLECTION_FIELD_SLUG_PATTERN.test(collection.slug)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.slug: must start with a letter and contain only lowercase letters, numbers, and underscores`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for duplicate slugs\n\t\t\t\t\tif (collectionSlugs.has(collection.slug)) {\n\t\t\t\t\t\terrors.push(`${prefix}.slug: duplicate collection slug \"${collection.slug}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tcollectionSlugs.add(collection.slug);\n\t\t\t\t}\n\n\t\t\t\tif (!collection.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\t// Validate fields\n\t\t\t\tif (!Array.isArray(collection.fields)) {\n\t\t\t\t\terrors.push(`${prefix}.fields: must be an array`);\n\t\t\t\t} else {\n\t\t\t\t\tconst fieldSlugs = new Set<string>();\n\n\t\t\t\t\tfor (let j = 0; j < collection.fields.length; j++) {\n\t\t\t\t\t\tconst field = collection.fields[j];\n\t\t\t\t\t\tconst fieldPrefix = `${prefix}.fields[${j}]`;\n\n\t\t\t\t\t\tif (!field.slug) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}: slug is required`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Check for valid slug format\n\t\t\t\t\t\t\tif (!COLLECTION_FIELD_SLUG_PATTERN.test(field.slug)) {\n\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t`${fieldPrefix}.slug: must start with a letter and contain only lowercase letters, numbers, and underscores`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Check for duplicate field slugs\n\t\t\t\t\t\t\tif (fieldSlugs.has(field.slug)) {\n\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t`${fieldPrefix}.slug: duplicate field slug \"${field.slug}\" in collection \"${collection.slug}\"`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfieldSlugs.add(field.slug);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!field.label) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}: label is required`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!field.type) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}: type is required`);\n\t\t\t\t\t\t} else if (!(FIELD_TYPES as readonly string[]).includes(field.type)) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}.type: unsupported field type \"${field.type}\"`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate taxonomies\n\tif (seed.taxonomies) {\n\t\tif (!Array.isArray(seed.taxonomies)) {\n\t\t\terrors.push(\"taxonomies must be an array\");\n\t\t} else {\n\t\t\tconst taxonomyNames = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.taxonomies.length; i++) {\n\t\t\t\tconst taxonomy = seed.taxonomies[i];\n\t\t\t\tconst prefix = `taxonomies[${i}]`;\n\n\t\t\t\tif (!taxonomy.name) {\n\t\t\t\t\terrors.push(`${prefix}: name is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Uniqueness is per (name, locale).\n\t\t\t\t\tconst key = `${taxonomy.name}::${taxonomy.locale ?? \"\"}`;\n\t\t\t\t\tif (taxonomyNames.has(key)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\ttaxonomy.locale\n\t\t\t\t\t\t\t\t? `${prefix}.name: duplicate taxonomy \"${taxonomy.name}\" in locale \"${taxonomy.locale}\"`\n\t\t\t\t\t\t\t\t: `${prefix}.name: duplicate taxonomy name \"${taxonomy.name}\"`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\ttaxonomyNames.add(key);\n\t\t\t\t}\n\n\t\t\t\tif (!taxonomy.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\tif (taxonomy.hierarchical === undefined) {\n\t\t\t\t\terrors.push(`${prefix}: hierarchical is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(taxonomy.collections)) {\n\t\t\t\t\terrors.push(`${prefix}.collections: must be an array`);\n\t\t\t\t} else if (taxonomy.collections.length === 0) {\n\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t`${prefix}.collections: taxonomy \"${taxonomy.name}\" is not assigned to any collections`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Validate terms if present\n\t\t\t\tif (taxonomy.terms) {\n\t\t\t\t\tif (!Array.isArray(taxonomy.terms)) {\n\t\t\t\t\t\terrors.push(`${prefix}.terms: must be an array`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst termSlugs = new Set<string>();\n\n\t\t\t\t\t\tfor (let j = 0; j < taxonomy.terms.length; j++) {\n\t\t\t\t\t\t\tconst term = taxonomy.terms[j];\n\t\t\t\t\t\t\tconst termPrefix = `${prefix}.terms[${j}]`;\n\n\t\t\t\t\t\t\tif (!term.slug) {\n\t\t\t\t\t\t\t\terrors.push(`${termPrefix}: slug is required`);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Uniqueness is per (slug, locale) so the same slug can repeat\n\t\t\t\t\t\t\t\t// across locale variants of the def.\n\t\t\t\t\t\t\t\tconst key = `${term.slug}::${term.locale ?? taxonomy.locale ?? \"\"}`;\n\t\t\t\t\t\t\t\tif (termSlugs.has(key)) {\n\t\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t\t`${termPrefix}.slug: duplicate term slug \"${term.slug}\" in taxonomy \"${taxonomy.name}\"`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttermSlugs.add(key);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (!term.label) {\n\t\t\t\t\t\t\t\terrors.push(`${termPrefix}: label is required`);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Check parent reference validity (for hierarchical taxonomies)\n\t\t\t\t\t\t\tif (term.parent && taxonomy.hierarchical) {\n\t\t\t\t\t\t\t\t// Parent will be validated in a second pass\n\t\t\t\t\t\t\t} else if (term.parent && !taxonomy.hierarchical) {\n\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t`${termPrefix}.parent: taxonomy \"${taxonomy.name}\" is not hierarchical, parent will be ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Second pass: validate parent references (within the same locale).\n\t\t\t\t\t\tif (taxonomy.hierarchical && taxonomy.terms) {\n\t\t\t\t\t\t\tfor (let j = 0; j < taxonomy.terms.length; j++) {\n\t\t\t\t\t\t\t\tconst term = taxonomy.terms[j];\n\t\t\t\t\t\t\t\tconst termLocale = term.locale ?? taxonomy.locale ?? \"\";\n\t\t\t\t\t\t\t\tif (term.parent && !termSlugs.has(`${term.parent}::${termLocale}`)) {\n\t\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t\t`${prefix}.terms[${j}].parent: parent term \"${term.parent}\" not found in taxonomy`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Check for circular references\n\t\t\t\t\t\t\t\tif (term.parent === term.slug) {\n\t\t\t\t\t\t\t\t\terrors.push(`${prefix}.terms[${j}].parent: term cannot be its own parent`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate menus\n\tif (seed.menus) {\n\t\tif (!Array.isArray(seed.menus)) {\n\t\t\terrors.push(\"menus must be an array\");\n\t\t} else {\n\t\t\tconst menuNames = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.menus.length; i++) {\n\t\t\t\tconst menu = seed.menus[i];\n\t\t\t\tconst prefix = `menus[${i}]`;\n\n\t\t\t\tif (!menu.name) {\n\t\t\t\t\terrors.push(`${prefix}: name is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Uniqueness is per (name, locale) — siblings of a translation\n\t\t\t\t\t// group share name but differ in locale.\n\t\t\t\t\tconst key = `${menu.name}::${menu.locale ?? \"\"}`;\n\t\t\t\t\tif (menuNames.has(key)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\tmenu.locale\n\t\t\t\t\t\t\t\t? `${prefix}.name: duplicate menu \"${menu.name}\" in locale \"${menu.locale}\"`\n\t\t\t\t\t\t\t\t: `${prefix}.name: duplicate menu name \"${menu.name}\"`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tmenuNames.add(key);\n\t\t\t\t}\n\n\t\t\t\tif (!menu.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(menu.items)) {\n\t\t\t\t\terrors.push(`${prefix}.items: must be an array`);\n\t\t\t\t} else {\n\t\t\t\t\tvalidateMenuItems(menu.items, prefix, errors, warnings);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate redirects\n\tif (seed.redirects) {\n\t\tif (!Array.isArray(seed.redirects)) {\n\t\t\terrors.push(\"redirects must be an array\");\n\t\t} else {\n\t\t\tconst redirectSources = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.redirects.length; i++) {\n\t\t\t\tconst redirect = seed.redirects[i];\n\t\t\t\tconst prefix = `redirects[${i}]`;\n\n\t\t\t\tif (!isRecord(redirect)) {\n\t\t\t\t\terrors.push(`${prefix}: must be an object`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst source = typeof redirect.source === \"string\" ? redirect.source : undefined;\n\t\t\t\tconst destination =\n\t\t\t\t\ttypeof redirect.destination === \"string\" ? redirect.destination : undefined;\n\n\t\t\t\tif (!source) {\n\t\t\t\t\terrors.push(`${prefix}: source is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (!isValidRedirectPath(source)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.source: must be a path starting with / (no protocol-relative URLs, path traversal, or newlines)`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (redirectSources.has(source)) {\n\t\t\t\t\t\terrors.push(`${prefix}.source: duplicate redirect source \"${source}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tredirectSources.add(source);\n\t\t\t\t}\n\n\t\t\t\tif (!destination) {\n\t\t\t\t\terrors.push(`${prefix}: destination is required`);\n\t\t\t\t} else if (!isValidRedirectPath(destination)) {\n\t\t\t\t\terrors.push(\n\t\t\t\t\t\t`${prefix}.destination: must be a path starting with / (no protocol-relative URLs, path traversal, or newlines)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (redirect.type !== undefined) {\n\t\t\t\t\tif (typeof redirect.type !== \"number\" || !REDIRECT_TYPES.has(redirect.type)) {\n\t\t\t\t\t\terrors.push(`${prefix}.type: must be 301, 302, 307, or 308`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (redirect.enabled !== undefined && typeof redirect.enabled !== \"boolean\") {\n\t\t\t\t\terrors.push(`${prefix}.enabled: must be a boolean`);\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\tredirect.groupName !== undefined &&\n\t\t\t\t\ttypeof redirect.groupName !== \"string\" &&\n\t\t\t\t\tredirect.groupName !== null\n\t\t\t\t) {\n\t\t\t\t\terrors.push(`${prefix}.groupName: must be a string or null`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate widget areas\n\tif (seed.widgetAreas) {\n\t\tif (!Array.isArray(seed.widgetAreas)) {\n\t\t\terrors.push(\"widgetAreas must be an array\");\n\t\t} else {\n\t\t\tconst areaNames = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.widgetAreas.length; i++) {\n\t\t\t\tconst area = seed.widgetAreas[i];\n\t\t\t\tconst prefix = `widgetAreas[${i}]`;\n\n\t\t\t\tif (!area.name) {\n\t\t\t\t\terrors.push(`${prefix}: name is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Check for duplicate area names\n\t\t\t\t\tif (areaNames.has(area.name)) {\n\t\t\t\t\t\terrors.push(`${prefix}.name: duplicate widget area name \"${area.name}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tareaNames.add(area.name);\n\t\t\t\t}\n\n\t\t\t\tif (!area.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(area.widgets)) {\n\t\t\t\t\terrors.push(`${prefix}.widgets: must be an array`);\n\t\t\t\t} else {\n\t\t\t\t\tfor (let j = 0; j < area.widgets.length; j++) {\n\t\t\t\t\t\tconst widget = area.widgets[j];\n\t\t\t\t\t\tconst widgetPrefix = `${prefix}.widgets[${j}]`;\n\n\t\t\t\t\t\tif (!widget.type) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}: type is required`);\n\t\t\t\t\t\t} else if (![\"content\", \"menu\", \"component\"].includes(widget.type)) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}.type: must be \"content\", \"menu\", or \"component\"`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Type-specific validation\n\t\t\t\t\t\tif (widget.type === \"menu\" && !widget.menuName) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}: menuName is required for menu widgets`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (widget.type === \"component\" && !widget.componentId) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}: componentId is required for component widgets`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate sections\n\tif (seed.sections) {\n\t\tif (!Array.isArray(seed.sections)) {\n\t\t\terrors.push(\"sections must be an array\");\n\t\t} else {\n\t\t\tconst sectionSlugs = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.sections.length; i++) {\n\t\t\t\tconst section = seed.sections[i];\n\t\t\t\tconst prefix = `sections[${i}]`;\n\n\t\t\t\tif (!section.slug) {\n\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (!SLUG_PATTERN.test(section.slug)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.slug: must contain only lowercase letters, numbers, and hyphens`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (sectionSlugs.has(section.slug)) {\n\t\t\t\t\t\terrors.push(`${prefix}.slug: duplicate section slug \"${section.slug}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tsectionSlugs.add(section.slug);\n\t\t\t\t}\n\n\t\t\t\tif (!section.title) {\n\t\t\t\t\terrors.push(`${prefix}: title is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(section.content)) {\n\t\t\t\t\terrors.push(`${prefix}.content: must be an array`);\n\t\t\t\t}\n\n\t\t\t\t// Validate source\n\t\t\t\tif (section.source && ![\"theme\", \"import\"].includes(section.source)) {\n\t\t\t\t\terrors.push(`${prefix}.source: must be \"theme\" or \"import\"`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate bylines\n\tif (seed.bylines) {\n\t\tif (!Array.isArray(seed.bylines)) {\n\t\t\terrors.push(\"bylines must be an array\");\n\t\t} else {\n\t\t\tconst bylineIds = new Set<string>();\n\t\t\tconst bylineSlugs = new Set<string>();\n\t\t\tfor (let i = 0; i < seed.bylines.length; i++) {\n\t\t\t\tconst byline = seed.bylines[i];\n\t\t\t\tconst prefix = `bylines[${i}]`;\n\n\t\t\t\tif (!byline.id) {\n\t\t\t\t\terrors.push(`${prefix}: id is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (bylineIds.has(byline.id)) {\n\t\t\t\t\t\terrors.push(`${prefix}.id: duplicate byline id \"${byline.id}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tbylineIds.add(byline.id);\n\t\t\t\t}\n\n\t\t\t\tif (!byline.slug) {\n\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (!SLUG_PATTERN.test(byline.slug)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.slug: must contain only lowercase letters, numbers, and hyphens`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (bylineSlugs.has(byline.slug)) {\n\t\t\t\t\t\terrors.push(`${prefix}.slug: duplicate byline slug \"${byline.slug}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tbylineSlugs.add(byline.slug);\n\t\t\t\t}\n\n\t\t\t\tif (!byline.displayName) {\n\t\t\t\t\terrors.push(`${prefix}: displayName is required`);\n\t\t\t\t}\n\n\t\t\t\tif (byline.avatar !== undefined) {\n\t\t\t\t\tconst avatar: unknown = byline.avatar;\n\t\t\t\t\tif (!isRecord(avatar)) {\n\t\t\t\t\t\terrors.push(`${prefix}.avatar: must be an object`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// storageKey is used verbatim in a `where storage_key = ?` lookup,\n\t\t\t\t\t\t// so surrounding whitespace would silently never match a real key.\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof avatar.storageKey !== \"string\" ||\n\t\t\t\t\t\t\tavatar.storageKey.length === 0 ||\n\t\t\t\t\t\t\tavatar.storageKey !== avatar.storageKey.trim()\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t`${prefix}.avatar.storageKey: must be a non-empty string with no leading or trailing whitespace`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const key of [\"alt\", \"filename\", \"mimeType\"] as const) {\n\t\t\t\t\t\t\tif (avatar[key] !== undefined && typeof avatar[key] !== \"string\") {\n\t\t\t\t\t\t\t\terrors.push(`${prefix}.avatar.${key}: must be a string`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// filename/mimeType are used verbatim, so reject blank or\n\t\t\t\t\t\t// whitespace-padded values (an empty filename would create a\n\t\t\t\t\t\t// media row with no basename; a padded mime type is invalid).\n\t\t\t\t\t\tfor (const key of [\"filename\", \"mimeType\"] as const) {\n\t\t\t\t\t\t\tconst v = avatar[key];\n\t\t\t\t\t\t\tif (typeof v === \"string\" && (v.length === 0 || v !== v.trim())) {\n\t\t\t\t\t\t\t\terrors.push(`${prefix}.avatar.${key}: must not be empty or whitespace-padded`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const key of [\"width\", \"height\"] as const) {\n\t\t\t\t\t\t\tconst v = avatar[key];\n\t\t\t\t\t\t\tif (v !== undefined && (typeof v !== \"number\" || !Number.isFinite(v) || v < 0)) {\n\t\t\t\t\t\t\t\terrors.push(`${prefix}.avatar.${key}: must be a non-negative number`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate content\n\tif (seed.content) {\n\t\tif (typeof seed.content !== \"object\" || Array.isArray(seed.content)) {\n\t\t\terrors.push(\"content must be an object (collection -> entries)\");\n\t\t} else {\n\t\t\tfor (const [collectionSlug, entries] of Object.entries(seed.content)) {\n\t\t\t\tif (!Array.isArray(entries)) {\n\t\t\t\t\terrors.push(`content.${collectionSlug}: must be an array`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst entryIds = new Set<string>();\n\n\t\t\t\tfor (let i = 0; i < entries.length; i++) {\n\t\t\t\t\tconst entry = entries[i];\n\t\t\t\t\tconst prefix = `content.${collectionSlug}[${i}]`;\n\n\t\t\t\t\tif (!entry.id) {\n\t\t\t\t\t\terrors.push(`${prefix}: id is required`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Check for duplicate entry IDs\n\t\t\t\t\t\tif (entryIds.has(entry.id)) {\n\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t`${prefix}.id: duplicate entry id \"${entry.id}\" in collection \"${collectionSlug}\"`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tentryIds.add(entry.id);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!entry.slug) {\n\t\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!entry.data || typeof entry.data !== \"object\") {\n\t\t\t\t\t\terrors.push(`${prefix}: data must be an object`);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Validate i18n fields\n\t\t\t\t\tif (entry.translationOf) {\n\t\t\t\t\t\tif (!entry.locale) {\n\t\t\t\t\t\t\terrors.push(`${prefix}: locale is required when translationOf is set`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Second pass: validate translationOf references within this collection\n\t\t\t\tfor (let i = 0; i < entries.length; i++) {\n\t\t\t\t\tconst entry = entries[i];\n\t\t\t\t\tif (entry.translationOf && !entryIds.has(entry.translationOf)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`content.${collectionSlug}[${i}].translationOf: references \"${entry.translationOf}\" which is not in this collection`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate cross-references (content refs in menus)\n\tif (seed.menus && seed.content) {\n\t\tconst allContentIds = new Set<string>();\n\t\tfor (const entries of Object.values(seed.content)) {\n\t\t\tif (Array.isArray(entries)) {\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (entry.id) {\n\t\t\t\t\t\tallContentIds.add(entry.id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check menu item refs\n\t\tfor (const menu of seed.menus) {\n\t\t\tif (Array.isArray(menu.items)) {\n\t\t\t\tvalidateMenuItemRefs(menu.items, allContentIds, warnings);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate byline refs in content\n\tif (seed.content) {\n\t\tconst seedBylineIds = new Set<string>((seed.bylines ?? []).map((byline) => byline.id));\n\t\tfor (const [collectionSlug, entries] of Object.entries(seed.content)) {\n\t\t\tif (!Array.isArray(entries)) continue;\n\t\t\tfor (let i = 0; i < entries.length; i++) {\n\t\t\t\tconst entry = entries[i];\n\t\t\t\tif (!entry.bylines) continue;\n\t\t\t\tif (!Array.isArray(entry.bylines)) {\n\t\t\t\t\terrors.push(`content.${collectionSlug}[${i}].bylines: must be an array`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfor (let j = 0; j < entry.bylines.length; j++) {\n\t\t\t\t\tconst credit = entry.bylines[j];\n\t\t\t\t\tconst prefix = `content.${collectionSlug}[${i}].bylines[${j}]`;\n\t\t\t\t\tif (!credit.byline) {\n\t\t\t\t\t\terrors.push(`${prefix}.byline: is required`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (!seedBylineIds.has(credit.byline)) {\n\t\t\t\t\t\terrors.push(`${prefix}.byline: references unknown byline \"${credit.byline}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tvalid: errors.length === 0,\n\t\terrors,\n\t\twarnings,\n\t};\n}\n\n/**\n * Validate menu items recursively\n */\nfunction validateMenuItems(\n\titems: unknown[],\n\tprefix: string,\n\terrors: string[],\n\twarnings: string[],\n): void {\n\tfor (let i = 0; i < items.length; i++) {\n\t\tconst raw = items[i];\n\t\tconst itemPrefix = `${prefix}.items[${i}]`;\n\n\t\tif (!isRecord(raw)) {\n\t\t\terrors.push(`${itemPrefix}: must be an object`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst item = raw;\n\n\t\tconst itemType = typeof item.type === \"string\" ? item.type : undefined;\n\n\t\tif (!itemType) {\n\t\t\terrors.push(`${itemPrefix}: type is required`);\n\t\t} else if (![\"custom\", \"page\", \"post\", \"taxonomy\", \"collection\"].includes(itemType)) {\n\t\t\terrors.push(\n\t\t\t\t`${itemPrefix}.type: must be \"custom\", \"page\", \"post\", \"taxonomy\", or \"collection\"`,\n\t\t\t);\n\t\t}\n\n\t\t// Type-specific validation\n\t\tif (itemType === \"custom\" && !item.url) {\n\t\t\terrors.push(`${itemPrefix}: url is required for custom menu items`);\n\t\t}\n\n\t\tif ((itemType === \"page\" || itemType === \"post\") && !item.ref) {\n\t\t\terrors.push(`${itemPrefix}: ref is required for page/post menu items`);\n\t\t}\n\n\t\t// Validate children recursively\n\t\tif (Array.isArray(item.children)) {\n\t\t\tvalidateMenuItems(item.children, itemPrefix, errors, warnings);\n\t\t}\n\t}\n}\n\n/**\n * Validate menu item references exist in content\n */\nfunction validateMenuItemRefs(\n\titems: SeedMenuItem[],\n\tcontentIds: Set<string>,\n\twarnings: string[],\n): void {\n\tfor (const item of items) {\n\t\tif ((item.type === \"page\" || item.type === \"post\") && item.ref) {\n\t\t\tif (!contentIds.has(item.ref)) {\n\t\t\t\twarnings.push(`Menu item references content \"${item.ref}\" which is not in the seed file`);\n\t\t\t}\n\t\t}\n\n\t\tif (item.children) {\n\t\t\tvalidateMenuItemRefs(item.children, contentIds, warnings);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validate-CuJ1Ps9j.mjs","names":[],"sources":["../src/seed/validate.ts"],"sourcesContent":["/**\n * Seed file validation\n *\n * Validates a seed file structure before applying it.\n */\n\nimport { FIELD_TYPES } from \"../schema/types.js\";\nimport type { SeedFile, SeedMenuItem, ValidationResult } from \"./types.js\";\n\nconst COLLECTION_FIELD_SLUG_PATTERN = /^[a-z][a-z0-9_]*$/;\nconst SLUG_PATTERN = /^[a-z0-9-]+$/;\nconst REDIRECT_TYPES = new Set([301, 302, 307, 308]);\nconst CRLF_PATTERN = /[\\r\\n]/;\n\n/** Type guard for Record<string, unknown> */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isValidRedirectPath(path: string): boolean {\n\tif (!path.startsWith(\"/\") || path.startsWith(\"//\") || CRLF_PATTERN.test(path)) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\treturn !decodeURIComponent(path).split(\"/\").includes(\"..\");\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Validate a seed file\n *\n * @param data - Unknown data to validate as a seed file\n * @returns Validation result with errors and warnings\n */\nexport function validateSeed(data: unknown): ValidationResult {\n\tconst errors: string[] = [];\n\tconst warnings: string[] = [];\n\n\t// Basic type check\n\tif (!data || typeof data !== \"object\") {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terrors: [\"Seed must be an object\"],\n\t\t\twarnings: [],\n\t\t};\n\t}\n\n\tconst seed = data as Partial<SeedFile>;\n\n\t// Required fields\n\tif (!seed.version) {\n\t\terrors.push(\"Seed must have a version field\");\n\t} else if (seed.version !== \"1\") {\n\t\terrors.push(`Unsupported seed version: ${String(seed.version)}`);\n\t}\n\n\t// defaultLocale backfills the locale of rows that omit one, so a blank or\n\t// whitespace-padded value would silently write empty/invalid locales to the DB.\n\t// Exported seeds never hit this, but it's part of the public schema now.\n\tif (seed.defaultLocale !== undefined) {\n\t\tif (\n\t\t\ttypeof seed.defaultLocale !== \"string\" ||\n\t\t\tseed.defaultLocale.length === 0 ||\n\t\t\tseed.defaultLocale !== seed.defaultLocale.trim()\n\t\t) {\n\t\t\terrors.push(\n\t\t\t\t\"defaultLocale: must be a non-empty string with no leading or trailing whitespace\",\n\t\t\t);\n\t\t}\n\t}\n\n\t// Validate collections\n\tif (seed.collections) {\n\t\tif (!Array.isArray(seed.collections)) {\n\t\t\terrors.push(\"collections must be an array\");\n\t\t} else {\n\t\t\tconst collectionSlugs = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.collections.length; i++) {\n\t\t\t\tconst collection = seed.collections[i];\n\t\t\t\tconst prefix = `collections[${i}]`;\n\n\t\t\t\tif (!collection.slug) {\n\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Check for valid slug format\n\t\t\t\t\tif (!COLLECTION_FIELD_SLUG_PATTERN.test(collection.slug)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.slug: must start with a letter and contain only lowercase letters, numbers, and underscores`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for duplicate slugs\n\t\t\t\t\tif (collectionSlugs.has(collection.slug)) {\n\t\t\t\t\t\terrors.push(`${prefix}.slug: duplicate collection slug \"${collection.slug}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tcollectionSlugs.add(collection.slug);\n\t\t\t\t}\n\n\t\t\t\tif (!collection.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\t// Validate fields\n\t\t\t\tif (!Array.isArray(collection.fields)) {\n\t\t\t\t\terrors.push(`${prefix}.fields: must be an array`);\n\t\t\t\t} else {\n\t\t\t\t\tconst fieldSlugs = new Set<string>();\n\n\t\t\t\t\tfor (let j = 0; j < collection.fields.length; j++) {\n\t\t\t\t\t\tconst field = collection.fields[j];\n\t\t\t\t\t\tconst fieldPrefix = `${prefix}.fields[${j}]`;\n\n\t\t\t\t\t\tif (!field.slug) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}: slug is required`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Check for valid slug format\n\t\t\t\t\t\t\tif (!COLLECTION_FIELD_SLUG_PATTERN.test(field.slug)) {\n\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t`${fieldPrefix}.slug: must start with a letter and contain only lowercase letters, numbers, and underscores`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Check for duplicate field slugs\n\t\t\t\t\t\t\tif (fieldSlugs.has(field.slug)) {\n\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t`${fieldPrefix}.slug: duplicate field slug \"${field.slug}\" in collection \"${collection.slug}\"`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfieldSlugs.add(field.slug);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!field.label) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}: label is required`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!field.type) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}: type is required`);\n\t\t\t\t\t\t} else if (!(FIELD_TYPES as readonly string[]).includes(field.type)) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}.type: unsupported field type \"${field.type}\"`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate taxonomies\n\tif (seed.taxonomies) {\n\t\tif (!Array.isArray(seed.taxonomies)) {\n\t\t\terrors.push(\"taxonomies must be an array\");\n\t\t} else {\n\t\t\tconst taxonomyNames = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.taxonomies.length; i++) {\n\t\t\t\tconst taxonomy = seed.taxonomies[i];\n\t\t\t\tconst prefix = `taxonomies[${i}]`;\n\n\t\t\t\tif (!taxonomy.name) {\n\t\t\t\t\terrors.push(`${prefix}: name is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Uniqueness is per (name, locale).\n\t\t\t\t\tconst key = `${taxonomy.name}::${taxonomy.locale ?? \"\"}`;\n\t\t\t\t\tif (taxonomyNames.has(key)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\ttaxonomy.locale\n\t\t\t\t\t\t\t\t? `${prefix}.name: duplicate taxonomy \"${taxonomy.name}\" in locale \"${taxonomy.locale}\"`\n\t\t\t\t\t\t\t\t: `${prefix}.name: duplicate taxonomy name \"${taxonomy.name}\"`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\ttaxonomyNames.add(key);\n\t\t\t\t}\n\n\t\t\t\tif (!taxonomy.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\tif (taxonomy.hierarchical === undefined) {\n\t\t\t\t\terrors.push(`${prefix}: hierarchical is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(taxonomy.collections)) {\n\t\t\t\t\terrors.push(`${prefix}.collections: must be an array`);\n\t\t\t\t} else if (taxonomy.collections.length === 0) {\n\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t`${prefix}.collections: taxonomy \"${taxonomy.name}\" is not assigned to any collections`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Validate terms if present\n\t\t\t\tif (taxonomy.terms) {\n\t\t\t\t\tif (!Array.isArray(taxonomy.terms)) {\n\t\t\t\t\t\terrors.push(`${prefix}.terms: must be an array`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst termSlugs = new Set<string>();\n\n\t\t\t\t\t\tfor (let j = 0; j < taxonomy.terms.length; j++) {\n\t\t\t\t\t\t\tconst term = taxonomy.terms[j];\n\t\t\t\t\t\t\tconst termPrefix = `${prefix}.terms[${j}]`;\n\n\t\t\t\t\t\t\tif (!term.slug) {\n\t\t\t\t\t\t\t\terrors.push(`${termPrefix}: slug is required`);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Uniqueness is per (slug, locale) so the same slug can repeat\n\t\t\t\t\t\t\t\t// across locale variants of the def.\n\t\t\t\t\t\t\t\tconst key = `${term.slug}::${term.locale ?? taxonomy.locale ?? \"\"}`;\n\t\t\t\t\t\t\t\tif (termSlugs.has(key)) {\n\t\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t\t`${termPrefix}.slug: duplicate term slug \"${term.slug}\" in taxonomy \"${taxonomy.name}\"`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttermSlugs.add(key);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (!term.label) {\n\t\t\t\t\t\t\t\terrors.push(`${termPrefix}: label is required`);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Check parent reference validity (for hierarchical taxonomies)\n\t\t\t\t\t\t\tif (term.parent && taxonomy.hierarchical) {\n\t\t\t\t\t\t\t\t// Parent will be validated in a second pass\n\t\t\t\t\t\t\t} else if (term.parent && !taxonomy.hierarchical) {\n\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t`${termPrefix}.parent: taxonomy \"${taxonomy.name}\" is not hierarchical, parent will be ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Second pass: validate parent references (within the same locale).\n\t\t\t\t\t\tif (taxonomy.hierarchical && taxonomy.terms) {\n\t\t\t\t\t\t\tfor (let j = 0; j < taxonomy.terms.length; j++) {\n\t\t\t\t\t\t\t\tconst term = taxonomy.terms[j];\n\t\t\t\t\t\t\t\tconst termLocale = term.locale ?? taxonomy.locale ?? \"\";\n\t\t\t\t\t\t\t\tif (term.parent && !termSlugs.has(`${term.parent}::${termLocale}`)) {\n\t\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t\t`${prefix}.terms[${j}].parent: parent term \"${term.parent}\" not found in taxonomy`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Check for circular references\n\t\t\t\t\t\t\t\tif (term.parent === term.slug) {\n\t\t\t\t\t\t\t\t\terrors.push(`${prefix}.terms[${j}].parent: term cannot be its own parent`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate menus\n\tif (seed.menus) {\n\t\tif (!Array.isArray(seed.menus)) {\n\t\t\terrors.push(\"menus must be an array\");\n\t\t} else {\n\t\t\tconst menuNames = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.menus.length; i++) {\n\t\t\t\tconst menu = seed.menus[i];\n\t\t\t\tconst prefix = `menus[${i}]`;\n\n\t\t\t\tif (!menu.name) {\n\t\t\t\t\terrors.push(`${prefix}: name is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Uniqueness is per (name, locale) — siblings of a translation\n\t\t\t\t\t// group share name but differ in locale.\n\t\t\t\t\tconst key = `${menu.name}::${menu.locale ?? \"\"}`;\n\t\t\t\t\tif (menuNames.has(key)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\tmenu.locale\n\t\t\t\t\t\t\t\t? `${prefix}.name: duplicate menu \"${menu.name}\" in locale \"${menu.locale}\"`\n\t\t\t\t\t\t\t\t: `${prefix}.name: duplicate menu name \"${menu.name}\"`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tmenuNames.add(key);\n\t\t\t\t}\n\n\t\t\t\tif (!menu.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(menu.items)) {\n\t\t\t\t\terrors.push(`${prefix}.items: must be an array`);\n\t\t\t\t} else {\n\t\t\t\t\tvalidateMenuItems(menu.items, prefix, errors, warnings);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate redirects\n\tif (seed.redirects) {\n\t\tif (!Array.isArray(seed.redirects)) {\n\t\t\terrors.push(\"redirects must be an array\");\n\t\t} else {\n\t\t\tconst redirectSources = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.redirects.length; i++) {\n\t\t\t\tconst redirect = seed.redirects[i];\n\t\t\t\tconst prefix = `redirects[${i}]`;\n\n\t\t\t\tif (!isRecord(redirect)) {\n\t\t\t\t\terrors.push(`${prefix}: must be an object`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst source = typeof redirect.source === \"string\" ? redirect.source : undefined;\n\t\t\t\tconst destination =\n\t\t\t\t\ttypeof redirect.destination === \"string\" ? redirect.destination : undefined;\n\n\t\t\t\tif (!source) {\n\t\t\t\t\terrors.push(`${prefix}: source is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (!isValidRedirectPath(source)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.source: must be a path starting with / (no protocol-relative URLs, path traversal, or newlines)`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (redirectSources.has(source)) {\n\t\t\t\t\t\terrors.push(`${prefix}.source: duplicate redirect source \"${source}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tredirectSources.add(source);\n\t\t\t\t}\n\n\t\t\t\tif (!destination) {\n\t\t\t\t\terrors.push(`${prefix}: destination is required`);\n\t\t\t\t} else if (!isValidRedirectPath(destination)) {\n\t\t\t\t\terrors.push(\n\t\t\t\t\t\t`${prefix}.destination: must be a path starting with / (no protocol-relative URLs, path traversal, or newlines)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (redirect.type !== undefined) {\n\t\t\t\t\tif (typeof redirect.type !== \"number\" || !REDIRECT_TYPES.has(redirect.type)) {\n\t\t\t\t\t\terrors.push(`${prefix}.type: must be 301, 302, 307, or 308`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (redirect.enabled !== undefined && typeof redirect.enabled !== \"boolean\") {\n\t\t\t\t\terrors.push(`${prefix}.enabled: must be a boolean`);\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\tredirect.groupName !== undefined &&\n\t\t\t\t\ttypeof redirect.groupName !== \"string\" &&\n\t\t\t\t\tredirect.groupName !== null\n\t\t\t\t) {\n\t\t\t\t\terrors.push(`${prefix}.groupName: must be a string or null`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate widget areas\n\tif (seed.widgetAreas) {\n\t\tif (!Array.isArray(seed.widgetAreas)) {\n\t\t\terrors.push(\"widgetAreas must be an array\");\n\t\t} else {\n\t\t\tconst areaNames = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.widgetAreas.length; i++) {\n\t\t\t\tconst area = seed.widgetAreas[i];\n\t\t\t\tconst prefix = `widgetAreas[${i}]`;\n\n\t\t\t\tif (!area.name) {\n\t\t\t\t\terrors.push(`${prefix}: name is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Check for duplicate area names\n\t\t\t\t\tif (areaNames.has(area.name)) {\n\t\t\t\t\t\terrors.push(`${prefix}.name: duplicate widget area name \"${area.name}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tareaNames.add(area.name);\n\t\t\t\t}\n\n\t\t\t\tif (!area.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(area.widgets)) {\n\t\t\t\t\terrors.push(`${prefix}.widgets: must be an array`);\n\t\t\t\t} else {\n\t\t\t\t\tfor (let j = 0; j < area.widgets.length; j++) {\n\t\t\t\t\t\tconst widget = area.widgets[j];\n\t\t\t\t\t\tconst widgetPrefix = `${prefix}.widgets[${j}]`;\n\n\t\t\t\t\t\tif (!widget.type) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}: type is required`);\n\t\t\t\t\t\t} else if (![\"content\", \"menu\", \"component\"].includes(widget.type)) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}.type: must be \"content\", \"menu\", or \"component\"`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Type-specific validation\n\t\t\t\t\t\tif (widget.type === \"menu\" && !widget.menuName) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}: menuName is required for menu widgets`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (widget.type === \"component\" && !widget.componentId) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}: componentId is required for component widgets`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate sections\n\tif (seed.sections) {\n\t\tif (!Array.isArray(seed.sections)) {\n\t\t\terrors.push(\"sections must be an array\");\n\t\t} else {\n\t\t\tconst sectionSlugs = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.sections.length; i++) {\n\t\t\t\tconst section = seed.sections[i];\n\t\t\t\tconst prefix = `sections[${i}]`;\n\n\t\t\t\tif (!section.slug) {\n\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (!SLUG_PATTERN.test(section.slug)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.slug: must contain only lowercase letters, numbers, and hyphens`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (sectionSlugs.has(section.slug)) {\n\t\t\t\t\t\terrors.push(`${prefix}.slug: duplicate section slug \"${section.slug}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tsectionSlugs.add(section.slug);\n\t\t\t\t}\n\n\t\t\t\tif (!section.title) {\n\t\t\t\t\terrors.push(`${prefix}: title is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(section.content)) {\n\t\t\t\t\terrors.push(`${prefix}.content: must be an array`);\n\t\t\t\t}\n\n\t\t\t\t// Validate source\n\t\t\t\tif (section.source && ![\"theme\", \"import\"].includes(section.source)) {\n\t\t\t\t\terrors.push(`${prefix}.source: must be \"theme\" or \"import\"`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate bylines\n\tif (seed.bylines) {\n\t\tif (!Array.isArray(seed.bylines)) {\n\t\t\terrors.push(\"bylines must be an array\");\n\t\t} else {\n\t\t\tconst bylineIds = new Set<string>();\n\t\t\tconst bylineSlugs = new Set<string>();\n\t\t\tfor (let i = 0; i < seed.bylines.length; i++) {\n\t\t\t\tconst byline = seed.bylines[i];\n\t\t\t\tconst prefix = `bylines[${i}]`;\n\n\t\t\t\tif (!byline.id) {\n\t\t\t\t\terrors.push(`${prefix}: id is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (bylineIds.has(byline.id)) {\n\t\t\t\t\t\terrors.push(`${prefix}.id: duplicate byline id \"${byline.id}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tbylineIds.add(byline.id);\n\t\t\t\t}\n\n\t\t\t\tif (!byline.slug) {\n\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (!SLUG_PATTERN.test(byline.slug)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.slug: must contain only lowercase letters, numbers, and hyphens`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (bylineSlugs.has(byline.slug)) {\n\t\t\t\t\t\terrors.push(`${prefix}.slug: duplicate byline slug \"${byline.slug}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tbylineSlugs.add(byline.slug);\n\t\t\t\t}\n\n\t\t\t\tif (!byline.displayName) {\n\t\t\t\t\terrors.push(`${prefix}: displayName is required`);\n\t\t\t\t}\n\n\t\t\t\tif (byline.avatar !== undefined) {\n\t\t\t\t\tconst avatar: unknown = byline.avatar;\n\t\t\t\t\tif (!isRecord(avatar)) {\n\t\t\t\t\t\terrors.push(`${prefix}.avatar: must be an object`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// storageKey is used verbatim in a `where storage_key = ?` lookup,\n\t\t\t\t\t\t// so surrounding whitespace would silently never match a real key.\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof avatar.storageKey !== \"string\" ||\n\t\t\t\t\t\t\tavatar.storageKey.length === 0 ||\n\t\t\t\t\t\t\tavatar.storageKey !== avatar.storageKey.trim()\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t`${prefix}.avatar.storageKey: must be a non-empty string with no leading or trailing whitespace`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const key of [\"alt\", \"filename\", \"mimeType\"] as const) {\n\t\t\t\t\t\t\tif (avatar[key] !== undefined && typeof avatar[key] !== \"string\") {\n\t\t\t\t\t\t\t\terrors.push(`${prefix}.avatar.${key}: must be a string`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// filename/mimeType are used verbatim, so reject blank or\n\t\t\t\t\t\t// whitespace-padded values (an empty filename would create a\n\t\t\t\t\t\t// media row with no basename; a padded mime type is invalid).\n\t\t\t\t\t\tfor (const key of [\"filename\", \"mimeType\"] as const) {\n\t\t\t\t\t\t\tconst v = avatar[key];\n\t\t\t\t\t\t\tif (typeof v === \"string\" && (v.length === 0 || v !== v.trim())) {\n\t\t\t\t\t\t\t\terrors.push(`${prefix}.avatar.${key}: must not be empty or whitespace-padded`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const key of [\"width\", \"height\"] as const) {\n\t\t\t\t\t\t\tconst v = avatar[key];\n\t\t\t\t\t\t\tif (v !== undefined && (typeof v !== \"number\" || !Number.isFinite(v) || v < 0)) {\n\t\t\t\t\t\t\t\terrors.push(`${prefix}.avatar.${key}: must be a non-negative number`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate content\n\tif (seed.content) {\n\t\tif (typeof seed.content !== \"object\" || Array.isArray(seed.content)) {\n\t\t\terrors.push(\"content must be an object (collection -> entries)\");\n\t\t} else {\n\t\t\tfor (const [collectionSlug, entries] of Object.entries(seed.content)) {\n\t\t\t\tif (!Array.isArray(entries)) {\n\t\t\t\t\terrors.push(`content.${collectionSlug}: must be an array`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst entryIds = new Set<string>();\n\n\t\t\t\tfor (let i = 0; i < entries.length; i++) {\n\t\t\t\t\tconst entry = entries[i];\n\t\t\t\t\tconst prefix = `content.${collectionSlug}[${i}]`;\n\n\t\t\t\t\tif (!entry.id) {\n\t\t\t\t\t\terrors.push(`${prefix}: id is required`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Check for duplicate entry IDs\n\t\t\t\t\t\tif (entryIds.has(entry.id)) {\n\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t`${prefix}.id: duplicate entry id \"${entry.id}\" in collection \"${collectionSlug}\"`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tentryIds.add(entry.id);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!entry.slug) {\n\t\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!entry.data || typeof entry.data !== \"object\") {\n\t\t\t\t\t\terrors.push(`${prefix}: data must be an object`);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Validate i18n fields\n\t\t\t\t\tif (entry.translationOf) {\n\t\t\t\t\t\tif (!entry.locale) {\n\t\t\t\t\t\t\terrors.push(`${prefix}: locale is required when translationOf is set`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Second pass: validate translationOf references within this collection\n\t\t\t\tfor (let i = 0; i < entries.length; i++) {\n\t\t\t\t\tconst entry = entries[i];\n\t\t\t\t\tif (entry.translationOf && !entryIds.has(entry.translationOf)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`content.${collectionSlug}[${i}].translationOf: references \"${entry.translationOf}\" which is not in this collection`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate cross-references (content refs in menus)\n\tif (seed.menus && seed.content) {\n\t\tconst allContentIds = new Set<string>();\n\t\tfor (const entries of Object.values(seed.content)) {\n\t\t\tif (Array.isArray(entries)) {\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (entry.id) {\n\t\t\t\t\t\tallContentIds.add(entry.id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check menu item refs\n\t\tfor (const menu of seed.menus) {\n\t\t\tif (Array.isArray(menu.items)) {\n\t\t\t\tvalidateMenuItemRefs(menu.items, allContentIds, warnings);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate byline refs in content\n\tif (seed.content) {\n\t\tconst seedBylineIds = new Set<string>((seed.bylines ?? []).map((byline) => byline.id));\n\t\tfor (const [collectionSlug, entries] of Object.entries(seed.content)) {\n\t\t\tif (!Array.isArray(entries)) continue;\n\t\t\tfor (let i = 0; i < entries.length; i++) {\n\t\t\t\tconst entry = entries[i];\n\t\t\t\tif (!entry.bylines) continue;\n\t\t\t\tif (!Array.isArray(entry.bylines)) {\n\t\t\t\t\terrors.push(`content.${collectionSlug}[${i}].bylines: must be an array`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfor (let j = 0; j < entry.bylines.length; j++) {\n\t\t\t\t\tconst credit = entry.bylines[j];\n\t\t\t\t\tconst prefix = `content.${collectionSlug}[${i}].bylines[${j}]`;\n\t\t\t\t\tif (!credit.byline) {\n\t\t\t\t\t\terrors.push(`${prefix}.byline: is required`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (!seedBylineIds.has(credit.byline)) {\n\t\t\t\t\t\terrors.push(`${prefix}.byline: references unknown byline \"${credit.byline}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tvalid: errors.length === 0,\n\t\terrors,\n\t\twarnings,\n\t};\n}\n\n/**\n * Validate menu items recursively\n */\nfunction validateMenuItems(\n\titems: unknown[],\n\tprefix: string,\n\terrors: string[],\n\twarnings: string[],\n): void {\n\tfor (let i = 0; i < items.length; i++) {\n\t\tconst raw = items[i];\n\t\tconst itemPrefix = `${prefix}.items[${i}]`;\n\n\t\tif (!isRecord(raw)) {\n\t\t\terrors.push(`${itemPrefix}: must be an object`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst item = raw;\n\n\t\tconst itemType = typeof item.type === \"string\" ? item.type : undefined;\n\n\t\tif (!itemType) {\n\t\t\terrors.push(`${itemPrefix}: type is required`);\n\t\t} else if (![\"custom\", \"page\", \"post\", \"taxonomy\", \"collection\"].includes(itemType)) {\n\t\t\terrors.push(\n\t\t\t\t`${itemPrefix}.type: must be \"custom\", \"page\", \"post\", \"taxonomy\", or \"collection\"`,\n\t\t\t);\n\t\t}\n\n\t\t// Type-specific validation\n\t\tif (itemType === \"custom\" && !item.url) {\n\t\t\terrors.push(`${itemPrefix}: url is required for custom menu items`);\n\t\t}\n\n\t\tif ((itemType === \"page\" || itemType === \"post\") && !item.ref) {\n\t\t\terrors.push(`${itemPrefix}: ref is required for page/post menu items`);\n\t\t}\n\n\t\t// Validate children recursively\n\t\tif (Array.isArray(item.children)) {\n\t\t\tvalidateMenuItems(item.children, itemPrefix, errors, warnings);\n\t\t}\n\t}\n}\n\n/**\n * Validate menu item references exist in content\n */\nfunction validateMenuItemRefs(\n\titems: SeedMenuItem[],\n\tcontentIds: Set<string>,\n\twarnings: string[],\n): void {\n\tfor (const item of items) {\n\t\tif ((item.type === \"page\" || item.type === \"post\") && item.ref) {\n\t\t\tif (!contentIds.has(item.ref)) {\n\t\t\t\twarnings.push(`Menu item references content \"${item.ref}\" which is not in the seed file`);\n\t\t\t}\n\t\t}\n\n\t\tif (item.children) {\n\t\t\tvalidateMenuItemRefs(item.children, contentIds, warnings);\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;;;;AASA,MAAM,gCAAgC;AACtC,MAAM,eAAe;AACrB,MAAM,iBAAiB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAI,CAAC;AACpD,MAAM,eAAe;;AAGrB,SAAS,SAAS,OAAkD;AACnE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAG5E,SAAS,oBAAoB,MAAuB;AACnD,KAAI,CAAC,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,KAAK,IAAI,aAAa,KAAK,KAAK,CAC5E,QAAO;AAGR,KAAI;AACH,SAAO,CAAC,mBAAmB,KAAK,CAAC,MAAM,IAAI,CAAC,SAAS,KAAK;SACnD;AACP,SAAO;;;;;;;;;AAUT,SAAgB,aAAa,MAAiC;CAC7D,MAAM,SAAmB,EAAE;CAC3B,MAAM,WAAqB,EAAE;AAG7B,KAAI,CAAC,QAAQ,OAAO,SAAS,SAC5B,QAAO;EACN,OAAO;EACP,QAAQ,CAAC,yBAAyB;EAClC,UAAU,EAAE;EACZ;CAGF,MAAM,OAAO;AAGb,KAAI,CAAC,KAAK,QACT,QAAO,KAAK,iCAAiC;UACnC,KAAK,YAAY,IAC3B,QAAO,KAAK,6BAA6B,OAAO,KAAK,QAAQ,GAAG;AAMjE,KAAI,KAAK,kBAAkB,QAC1B;MACC,OAAO,KAAK,kBAAkB,YAC9B,KAAK,cAAc,WAAW,KAC9B,KAAK,kBAAkB,KAAK,cAAc,MAAM,CAEhD,QAAO,KACN,mFACA;;AAKH,KAAI,KAAK,YACR,KAAI,CAAC,MAAM,QAAQ,KAAK,YAAY,CACnC,QAAO,KAAK,+BAA+B;MACrC;EACN,MAAM,kCAAkB,IAAI,KAAa;AAEzC,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,YAAY,QAAQ,KAAK;GACjD,MAAM,aAAa,KAAK,YAAY;GACpC,MAAM,SAAS,eAAe,EAAE;AAEhC,OAAI,CAAC,WAAW,KACf,QAAO,KAAK,GAAG,OAAO,oBAAoB;QACpC;AAEN,QAAI,CAAC,8BAA8B,KAAK,WAAW,KAAK,CACvD,QAAO,KACN,GAAG,OAAO,8FACV;AAIF,QAAI,gBAAgB,IAAI,WAAW,KAAK,CACvC,QAAO,KAAK,GAAG,OAAO,oCAAoC,WAAW,KAAK,GAAG;AAE9E,oBAAgB,IAAI,WAAW,KAAK;;AAGrC,OAAI,CAAC,WAAW,MACf,QAAO,KAAK,GAAG,OAAO,qBAAqB;AAI5C,OAAI,CAAC,MAAM,QAAQ,WAAW,OAAO,CACpC,QAAO,KAAK,GAAG,OAAO,2BAA2B;QAC3C;IACN,MAAM,6BAAa,IAAI,KAAa;AAEpC,SAAK,IAAI,IAAI,GAAG,IAAI,WAAW,OAAO,QAAQ,KAAK;KAClD,MAAM,QAAQ,WAAW,OAAO;KAChC,MAAM,cAAc,GAAG,OAAO,UAAU,EAAE;AAE1C,SAAI,CAAC,MAAM,KACV,QAAO,KAAK,GAAG,YAAY,oBAAoB;UACzC;AAEN,UAAI,CAAC,8BAA8B,KAAK,MAAM,KAAK,CAClD,QAAO,KACN,GAAG,YAAY,8FACf;AAIF,UAAI,WAAW,IAAI,MAAM,KAAK,CAC7B,QAAO,KACN,GAAG,YAAY,+BAA+B,MAAM,KAAK,mBAAmB,WAAW,KAAK,GAC5F;AAEF,iBAAW,IAAI,MAAM,KAAK;;AAG3B,SAAI,CAAC,MAAM,MACV,QAAO,KAAK,GAAG,YAAY,qBAAqB;AAGjD,SAAI,CAAC,MAAM,KACV,QAAO,KAAK,GAAG,YAAY,oBAAoB;cACrC,CAAE,YAAkC,SAAS,MAAM,KAAK,CAClE,QAAO,KAAK,GAAG,YAAY,iCAAiC,MAAM,KAAK,GAAG;;;;;AAShF,KAAI,KAAK,WACR,KAAI,CAAC,MAAM,QAAQ,KAAK,WAAW,CAClC,QAAO,KAAK,8BAA8B;MACpC;EACN,MAAM,gCAAgB,IAAI,KAAa;AAEvC,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,WAAW,QAAQ,KAAK;GAChD,MAAM,WAAW,KAAK,WAAW;GACjC,MAAM,SAAS,cAAc,EAAE;AAE/B,OAAI,CAAC,SAAS,KACb,QAAO,KAAK,GAAG,OAAO,oBAAoB;QACpC;IAEN,MAAM,MAAM,GAAG,SAAS,KAAK,IAAI,SAAS,UAAU;AACpD,QAAI,cAAc,IAAI,IAAI,CACzB,QAAO,KACN,SAAS,SACN,GAAG,OAAO,6BAA6B,SAAS,KAAK,eAAe,SAAS,OAAO,KACpF,GAAG,OAAO,kCAAkC,SAAS,KAAK,GAC7D;AAEF,kBAAc,IAAI,IAAI;;AAGvB,OAAI,CAAC,SAAS,MACb,QAAO,KAAK,GAAG,OAAO,qBAAqB;AAG5C,OAAI,SAAS,iBAAiB,OAC7B,QAAO,KAAK,GAAG,OAAO,4BAA4B;AAGnD,OAAI,CAAC,MAAM,QAAQ,SAAS,YAAY,CACvC,QAAO,KAAK,GAAG,OAAO,gCAAgC;YAC5C,SAAS,YAAY,WAAW,EAC1C,UAAS,KACR,GAAG,OAAO,0BAA0B,SAAS,KAAK,sCAClD;AAIF,OAAI,SAAS,MACZ,KAAI,CAAC,MAAM,QAAQ,SAAS,MAAM,CACjC,QAAO,KAAK,GAAG,OAAO,0BAA0B;QAC1C;IACN,MAAM,4BAAY,IAAI,KAAa;AAEnC,SAAK,IAAI,IAAI,GAAG,IAAI,SAAS,MAAM,QAAQ,KAAK;KAC/C,MAAM,OAAO,SAAS,MAAM;KAC5B,MAAM,aAAa,GAAG,OAAO,SAAS,EAAE;AAExC,SAAI,CAAC,KAAK,KACT,QAAO,KAAK,GAAG,WAAW,oBAAoB;UACxC;MAGN,MAAM,MAAM,GAAG,KAAK,KAAK,IAAI,KAAK,UAAU,SAAS,UAAU;AAC/D,UAAI,UAAU,IAAI,IAAI,CACrB,QAAO,KACN,GAAG,WAAW,8BAA8B,KAAK,KAAK,iBAAiB,SAAS,KAAK,GACrF;AAEF,gBAAU,IAAI,IAAI;;AAGnB,SAAI,CAAC,KAAK,MACT,QAAO,KAAK,GAAG,WAAW,qBAAqB;AAIhD,SAAI,KAAK,UAAU,SAAS,cAAc,YAE/B,KAAK,UAAU,CAAC,SAAS,aACnC,UAAS,KACR,GAAG,WAAW,qBAAqB,SAAS,KAAK,+CACjD;;AAKH,QAAI,SAAS,gBAAgB,SAAS,MACrC,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,MAAM,QAAQ,KAAK;KAC/C,MAAM,OAAO,SAAS,MAAM;KAC5B,MAAM,aAAa,KAAK,UAAU,SAAS,UAAU;AACrD,SAAI,KAAK,UAAU,CAAC,UAAU,IAAI,GAAG,KAAK,OAAO,IAAI,aAAa,CACjE,QAAO,KACN,GAAG,OAAO,SAAS,EAAE,yBAAyB,KAAK,OAAO,yBAC1D;AAIF,SAAI,KAAK,WAAW,KAAK,KACxB,QAAO,KAAK,GAAG,OAAO,SAAS,EAAE,yCAAyC;;;;;AAWlF,KAAI,KAAK,MACR,KAAI,CAAC,MAAM,QAAQ,KAAK,MAAM,CAC7B,QAAO,KAAK,yBAAyB;MAC/B;EACN,MAAM,4BAAY,IAAI,KAAa;AAEnC,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;GAC3C,MAAM,OAAO,KAAK,MAAM;GACxB,MAAM,SAAS,SAAS,EAAE;AAE1B,OAAI,CAAC,KAAK,KACT,QAAO,KAAK,GAAG,OAAO,oBAAoB;QACpC;IAGN,MAAM,MAAM,GAAG,KAAK,KAAK,IAAI,KAAK,UAAU;AAC5C,QAAI,UAAU,IAAI,IAAI,CACrB,QAAO,KACN,KAAK,SACF,GAAG,OAAO,yBAAyB,KAAK,KAAK,eAAe,KAAK,OAAO,KACxE,GAAG,OAAO,8BAA8B,KAAK,KAAK,GACrD;AAEF,cAAU,IAAI,IAAI;;AAGnB,OAAI,CAAC,KAAK,MACT,QAAO,KAAK,GAAG,OAAO,qBAAqB;AAG5C,OAAI,CAAC,MAAM,QAAQ,KAAK,MAAM,CAC7B,QAAO,KAAK,GAAG,OAAO,0BAA0B;OAEhD,mBAAkB,KAAK,OAAO,QAAQ,QAAQ,SAAS;;;AAO3D,KAAI,KAAK,UACR,KAAI,CAAC,MAAM,QAAQ,KAAK,UAAU,CACjC,QAAO,KAAK,6BAA6B;MACnC;EACN,MAAM,kCAAkB,IAAI,KAAa;AAEzC,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,UAAU,QAAQ,KAAK;GAC/C,MAAM,WAAW,KAAK,UAAU;GAChC,MAAM,SAAS,aAAa,EAAE;AAE9B,OAAI,CAAC,SAAS,SAAS,EAAE;AACxB,WAAO,KAAK,GAAG,OAAO,qBAAqB;AAC3C;;GAGD,MAAM,SAAS,OAAO,SAAS,WAAW,WAAW,SAAS,SAAS;GACvE,MAAM,cACL,OAAO,SAAS,gBAAgB,WAAW,SAAS,cAAc;AAEnE,OAAI,CAAC,OACJ,QAAO,KAAK,GAAG,OAAO,sBAAsB;QACtC;AACN,QAAI,CAAC,oBAAoB,OAAO,CAC/B,QAAO,KACN,GAAG,OAAO,kGACV;AAGF,QAAI,gBAAgB,IAAI,OAAO,CAC9B,QAAO,KAAK,GAAG,OAAO,sCAAsC,OAAO,GAAG;AAEvE,oBAAgB,IAAI,OAAO;;AAG5B,OAAI,CAAC,YACJ,QAAO,KAAK,GAAG,OAAO,2BAA2B;YACvC,CAAC,oBAAoB,YAAY,CAC3C,QAAO,KACN,GAAG,OAAO,uGACV;AAGF,OAAI,SAAS,SAAS,QACrB;QAAI,OAAO,SAAS,SAAS,YAAY,CAAC,eAAe,IAAI,SAAS,KAAK,CAC1E,QAAO,KAAK,GAAG,OAAO,sCAAsC;;AAI9D,OAAI,SAAS,YAAY,UAAa,OAAO,SAAS,YAAY,UACjE,QAAO,KAAK,GAAG,OAAO,6BAA6B;AAGpD,OACC,SAAS,cAAc,UACvB,OAAO,SAAS,cAAc,YAC9B,SAAS,cAAc,KAEvB,QAAO,KAAK,GAAG,OAAO,sCAAsC;;;AAOhE,KAAI,KAAK,YACR,KAAI,CAAC,MAAM,QAAQ,KAAK,YAAY,CACnC,QAAO,KAAK,+BAA+B;MACrC;EACN,MAAM,4BAAY,IAAI,KAAa;AAEnC,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,YAAY,QAAQ,KAAK;GACjD,MAAM,OAAO,KAAK,YAAY;GAC9B,MAAM,SAAS,eAAe,EAAE;AAEhC,OAAI,CAAC,KAAK,KACT,QAAO,KAAK,GAAG,OAAO,oBAAoB;QACpC;AAEN,QAAI,UAAU,IAAI,KAAK,KAAK,CAC3B,QAAO,KAAK,GAAG,OAAO,qCAAqC,KAAK,KAAK,GAAG;AAEzE,cAAU,IAAI,KAAK,KAAK;;AAGzB,OAAI,CAAC,KAAK,MACT,QAAO,KAAK,GAAG,OAAO,qBAAqB;AAG5C,OAAI,CAAC,MAAM,QAAQ,KAAK,QAAQ,CAC/B,QAAO,KAAK,GAAG,OAAO,4BAA4B;OAElD,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,QAAQ,KAAK;IAC7C,MAAM,SAAS,KAAK,QAAQ;IAC5B,MAAM,eAAe,GAAG,OAAO,WAAW,EAAE;AAE5C,QAAI,CAAC,OAAO,KACX,QAAO,KAAK,GAAG,aAAa,oBAAoB;aACtC,CAAC;KAAC;KAAW;KAAQ;KAAY,CAAC,SAAS,OAAO,KAAK,CACjE,QAAO,KAAK,GAAG,aAAa,kDAAkD;AAI/E,QAAI,OAAO,SAAS,UAAU,CAAC,OAAO,SACrC,QAAO,KAAK,GAAG,aAAa,yCAAyC;AAGtE,QAAI,OAAO,SAAS,eAAe,CAAC,OAAO,YAC1C,QAAO,KAAK,GAAG,aAAa,iDAAiD;;;;AASnF,KAAI,KAAK,SACR,KAAI,CAAC,MAAM,QAAQ,KAAK,SAAS,CAChC,QAAO,KAAK,4BAA4B;MAClC;EACN,MAAM,+BAAe,IAAI,KAAa;AAEtC,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;GAC9C,MAAM,UAAU,KAAK,SAAS;GAC9B,MAAM,SAAS,YAAY,EAAE;AAE7B,OAAI,CAAC,QAAQ,KACZ,QAAO,KAAK,GAAG,OAAO,oBAAoB;QACpC;AACN,QAAI,CAAC,aAAa,KAAK,QAAQ,KAAK,CACnC,QAAO,KACN,GAAG,OAAO,kEACV;AAEF,QAAI,aAAa,IAAI,QAAQ,KAAK,CACjC,QAAO,KAAK,GAAG,OAAO,iCAAiC,QAAQ,KAAK,GAAG;AAExE,iBAAa,IAAI,QAAQ,KAAK;;AAG/B,OAAI,CAAC,QAAQ,MACZ,QAAO,KAAK,GAAG,OAAO,qBAAqB;AAG5C,OAAI,CAAC,MAAM,QAAQ,QAAQ,QAAQ,CAClC,QAAO,KAAK,GAAG,OAAO,4BAA4B;AAInD,OAAI,QAAQ,UAAU,CAAC,CAAC,SAAS,SAAS,CAAC,SAAS,QAAQ,OAAO,CAClE,QAAO,KAAK,GAAG,OAAO,sCAAsC;;;AAOhE,KAAI,KAAK,QACR,KAAI,CAAC,MAAM,QAAQ,KAAK,QAAQ,CAC/B,QAAO,KAAK,2BAA2B;MACjC;EACN,MAAM,4BAAY,IAAI,KAAa;EACnC,MAAM,8BAAc,IAAI,KAAa;AACrC,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,QAAQ,KAAK;GAC7C,MAAM,SAAS,KAAK,QAAQ;GAC5B,MAAM,SAAS,WAAW,EAAE;AAE5B,OAAI,CAAC,OAAO,GACX,QAAO,KAAK,GAAG,OAAO,kBAAkB;QAClC;AACN,QAAI,UAAU,IAAI,OAAO,GAAG,CAC3B,QAAO,KAAK,GAAG,OAAO,4BAA4B,OAAO,GAAG,GAAG;AAEhE,cAAU,IAAI,OAAO,GAAG;;AAGzB,OAAI,CAAC,OAAO,KACX,QAAO,KAAK,GAAG,OAAO,oBAAoB;QACpC;AACN,QAAI,CAAC,aAAa,KAAK,OAAO,KAAK,CAClC,QAAO,KACN,GAAG,OAAO,kEACV;AAEF,QAAI,YAAY,IAAI,OAAO,KAAK,CAC/B,QAAO,KAAK,GAAG,OAAO,gCAAgC,OAAO,KAAK,GAAG;AAEtE,gBAAY,IAAI,OAAO,KAAK;;AAG7B,OAAI,CAAC,OAAO,YACX,QAAO,KAAK,GAAG,OAAO,2BAA2B;AAGlD,OAAI,OAAO,WAAW,QAAW;IAChC,MAAM,SAAkB,OAAO;AAC/B,QAAI,CAAC,SAAS,OAAO,CACpB,QAAO,KAAK,GAAG,OAAO,4BAA4B;SAC5C;AAGN,SACC,OAAO,OAAO,eAAe,YAC7B,OAAO,WAAW,WAAW,KAC7B,OAAO,eAAe,OAAO,WAAW,MAAM,CAE9C,QAAO,KACN,GAAG,OAAO,uFACV;AAEF,UAAK,MAAM,OAAO;MAAC;MAAO;MAAY;MAAW,CAChD,KAAI,OAAO,SAAS,UAAa,OAAO,OAAO,SAAS,SACvD,QAAO,KAAK,GAAG,OAAO,UAAU,IAAI,oBAAoB;AAM1D,UAAK,MAAM,OAAO,CAAC,YAAY,WAAW,EAAW;MACpD,MAAM,IAAI,OAAO;AACjB,UAAI,OAAO,MAAM,aAAa,EAAE,WAAW,KAAK,MAAM,EAAE,MAAM,EAC7D,QAAO,KAAK,GAAG,OAAO,UAAU,IAAI,0CAA0C;;AAGhF,UAAK,MAAM,OAAO,CAAC,SAAS,SAAS,EAAW;MAC/C,MAAM,IAAI,OAAO;AACjB,UAAI,MAAM,WAAc,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,EAAE,IAAI,IAAI,GAC3E,QAAO,KAAK,GAAG,OAAO,UAAU,IAAI,iCAAiC;;;;;;AAU5E,KAAI,KAAK,QACR,KAAI,OAAO,KAAK,YAAY,YAAY,MAAM,QAAQ,KAAK,QAAQ,CAClE,QAAO,KAAK,oDAAoD;KAEhE,MAAK,MAAM,CAAC,gBAAgB,YAAY,OAAO,QAAQ,KAAK,QAAQ,EAAE;AACrE,MAAI,CAAC,MAAM,QAAQ,QAAQ,EAAE;AAC5B,UAAO,KAAK,WAAW,eAAe,oBAAoB;AAC1D;;EAGD,MAAM,2BAAW,IAAI,KAAa;AAElC,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;GACxC,MAAM,QAAQ,QAAQ;GACtB,MAAM,SAAS,WAAW,eAAe,GAAG,EAAE;AAE9C,OAAI,CAAC,MAAM,GACV,QAAO,KAAK,GAAG,OAAO,kBAAkB;QAClC;AAEN,QAAI,SAAS,IAAI,MAAM,GAAG,CACzB,QAAO,KACN,GAAG,OAAO,2BAA2B,MAAM,GAAG,mBAAmB,eAAe,GAChF;AAEF,aAAS,IAAI,MAAM,GAAG;;AAGvB,OAAI,CAAC,MAAM,KACV,QAAO,KAAK,GAAG,OAAO,oBAAoB;AAG3C,OAAI,CAAC,MAAM,QAAQ,OAAO,MAAM,SAAS,SACxC,QAAO,KAAK,GAAG,OAAO,0BAA0B;AAIjD,OAAI,MAAM,eACT;QAAI,CAAC,MAAM,OACV,QAAO,KAAK,GAAG,OAAO,gDAAgD;;;AAMzE,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;GACxC,MAAM,QAAQ,QAAQ;AACtB,OAAI,MAAM,iBAAiB,CAAC,SAAS,IAAI,MAAM,cAAc,CAC5D,QAAO,KACN,WAAW,eAAe,GAAG,EAAE,+BAA+B,MAAM,cAAc,mCAClF;;;AAQN,KAAI,KAAK,SAAS,KAAK,SAAS;EAC/B,MAAM,gCAAgB,IAAI,KAAa;AACvC,OAAK,MAAM,WAAW,OAAO,OAAO,KAAK,QAAQ,CAChD,KAAI,MAAM,QAAQ,QAAQ,EACzB;QAAK,MAAM,SAAS,QACnB,KAAI,MAAM,GACT,eAAc,IAAI,MAAM,GAAG;;AAO/B,OAAK,MAAM,QAAQ,KAAK,MACvB,KAAI,MAAM,QAAQ,KAAK,MAAM,CAC5B,sBAAqB,KAAK,OAAO,eAAe,SAAS;;AAM5D,KAAI,KAAK,SAAS;EACjB,MAAM,gBAAgB,IAAI,KAAa,KAAK,WAAW,EAAE,EAAE,KAAK,WAAW,OAAO,GAAG,CAAC;AACtF,OAAK,MAAM,CAAC,gBAAgB,YAAY,OAAO,QAAQ,KAAK,QAAQ,EAAE;AACrE,OAAI,CAAC,MAAM,QAAQ,QAAQ,CAAE;AAC7B,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;IACxC,MAAM,QAAQ,QAAQ;AACtB,QAAI,CAAC,MAAM,QAAS;AACpB,QAAI,CAAC,MAAM,QAAQ,MAAM,QAAQ,EAAE;AAClC,YAAO,KAAK,WAAW,eAAe,GAAG,EAAE,6BAA6B;AACxE;;AAED,SAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,QAAQ,KAAK;KAC9C,MAAM,SAAS,MAAM,QAAQ;KAC7B,MAAM,SAAS,WAAW,eAAe,GAAG,EAAE,YAAY,EAAE;AAC5D,SAAI,CAAC,OAAO,QAAQ;AACnB,aAAO,KAAK,GAAG,OAAO,sBAAsB;AAC5C;;AAED,SAAI,CAAC,cAAc,IAAI,OAAO,OAAO,CACpC,QAAO,KAAK,GAAG,OAAO,sCAAsC,OAAO,OAAO,GAAG;;;;;AAOlF,QAAO;EACN,OAAO,OAAO,WAAW;EACzB;EACA;EACA;;;;;AAMF,SAAS,kBACR,OACA,QACA,QACA,UACO;AACP,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACtC,MAAM,MAAM,MAAM;EAClB,MAAM,aAAa,GAAG,OAAO,SAAS,EAAE;AAExC,MAAI,CAAC,SAAS,IAAI,EAAE;AACnB,UAAO,KAAK,GAAG,WAAW,qBAAqB;AAC/C;;EAGD,MAAM,OAAO;EAEb,MAAM,WAAW,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AAE7D,MAAI,CAAC,SACJ,QAAO,KAAK,GAAG,WAAW,oBAAoB;WACpC,CAAC;GAAC;GAAU;GAAQ;GAAQ;GAAY;GAAa,CAAC,SAAS,SAAS,CAClF,QAAO,KACN,GAAG,WAAW,sEACd;AAIF,MAAI,aAAa,YAAY,CAAC,KAAK,IAClC,QAAO,KAAK,GAAG,WAAW,yCAAyC;AAGpE,OAAK,aAAa,UAAU,aAAa,WAAW,CAAC,KAAK,IACzD,QAAO,KAAK,GAAG,WAAW,4CAA4C;AAIvE,MAAI,MAAM,QAAQ,KAAK,SAAS,CAC/B,mBAAkB,KAAK,UAAU,YAAY,QAAQ,SAAS;;;;;;AAQjE,SAAS,qBACR,OACA,YACA,UACO;AACP,MAAK,MAAM,QAAQ,OAAO;AACzB,OAAK,KAAK,SAAS,UAAU,KAAK,SAAS,WAAW,KAAK,KAC1D;OAAI,CAAC,WAAW,IAAI,KAAK,IAAI,CAC5B,UAAS,KAAK,iCAAiC,KAAK,IAAI,iCAAiC;;AAI3F,MAAI,KAAK,SACR,sBAAqB,KAAK,UAAU,YAAY,SAAS"}
@@ -1,10 +1,10 @@
1
1
  import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
2
2
  import "./dialect-helpers-DRI5pyY3.mjs";
3
- import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-BHgcHh2C.mjs";
3
+ import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-OCG163rN.mjs";
4
4
  import { n as isMissingTableError } from "./db-errors-CK46D-ly.mjs";
5
- import "./fts-manager-DW_NR-Fs.mjs";
6
- import { n as SchemaRegistry } from "./registry-3aOAS4My.mjs";
7
- import { n as generateZodSchema } from "./zod-generator-xEHODMJf.mjs";
5
+ import "./fts-manager-BbCCbEKA.mjs";
6
+ import { n as SchemaRegistry } from "./registry-g8yYt4cC.mjs";
7
+ import { n as generateZodSchema } from "./zod-generator-gFsRuNNy.mjs";
8
8
  import { sql } from "kysely";
9
9
 
10
10
  //#region src/api/handlers/validation.ts
@@ -142,4 +142,4 @@ async function validateContentData(db, collection, data, options = {}) {
142
142
 
143
143
  //#endregion
144
144
  export { validateContentData };
145
- //# sourceMappingURL=validation-B6DKPTtU.mjs.map
145
+ //# sourceMappingURL=validation-CwUfTdGF.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation-B6DKPTtU.mjs","names":[],"sources":["../src/api/handlers/validation.ts"],"sourcesContent":["/**\n * Field-level validation for content create / update.\n *\n * Wires the existing `generateZodSchema()` pipeline (`schema/zod-generator.ts`)\n * into the handler boundary so REST and MCP both get the same enforcement:\n *\n * - required fields must be present and non-empty\n * - select / multiSelect values must match the configured options\n * - reference fields must resolve to a real, non-trashed target\n *\n * Errors surface as `{ code: \"VALIDATION_ERROR\", message }` with all\n * offending fields listed in one message so callers can fix everything in\n * a single round trip.\n */\n\nimport { sql, type Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\nimport { SchemaRegistry } from \"../../schema/registry.js\";\nimport type { Field } from \"../../schema/types.js\";\nimport { generateZodSchema } from \"../../schema/zod-generator.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../../utils/chunks.js\";\nimport { isMissingTableError } from \"../../utils/db-errors.js\";\n\ntype ValidationResult =\n\t| { ok: true }\n\t| { ok: false; error: { code: \"VALIDATION_ERROR\" | \"COLLECTION_NOT_FOUND\"; message: string } };\n\n/** Treat `undefined`, `null`, and `\"\"` as \"not set\". */\nfunction isMissing(value: unknown): boolean {\n\treturn value === undefined || value === null || value === \"\";\n}\n\n/**\n * Resolve the target collection slug for a reference field.\n *\n * Schema-defined reference fields (the static `reference()` factory in\n * `fields/reference.ts`) put the target in `options.collection`. The MCP\n * `schema_create_field` tool also puts it there. Tests and some admin paths\n * stash it inside `validation.collection` directly; we accept both.\n */\nfunction getReferenceTargetCollection(field: Field): string | undefined {\n\tconst fromOptions = field.options?.collection;\n\tif (typeof fromOptions === \"string\" && fromOptions.length > 0) return fromOptions;\n\tconst validation = field.validation;\n\tif (validation && \"collection\" in validation) {\n\t\tconst fromValidation: unknown = (validation as { collection?: unknown }).collection;\n\t\tif (typeof fromValidation === \"string\" && fromValidation.length > 0) return fromValidation;\n\t}\n\treturn undefined;\n}\n\n/**\n * Format a Zod issue path into a human-readable field reference, e.g.\n * `tags`, `tags.1`, `image.alt`.\n */\nfunction formatIssuePath(path: ReadonlyArray<PropertyKey>): string {\n\tif (path.length === 0) return \"(root)\";\n\treturn path.map((seg) => String(seg)).join(\".\");\n}\n\n/**\n * Validate `data` against the collection's field definitions.\n *\n * `partial: true` switches Zod into partial mode so updates can include\n * only the fields being changed without tripping required-field errors on\n * fields the caller didn't touch. Required fields that ARE present in\n * partial-mode data still get the empty-string check below.\n */\nexport async function validateContentData(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tdata: Record<string, unknown>,\n\toptions: { partial?: boolean } = {},\n): Promise<ValidationResult> {\n\tconst registry = new SchemaRegistry(db);\n\tconst collectionWithFields = await registry.getCollectionWithFields(collection);\n\tif (!collectionWithFields) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: {\n\t\t\t\tcode: \"COLLECTION_NOT_FOUND\",\n\t\t\t\tmessage: `Collection '${collection}' not found`,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst issues: string[] = [];\n\n\t// Detect unknown keys explicitly so callers get a useful error rather\n\t// than silently dropped data. Leading-underscore keys (e.g. `_slug`,\n\t// `_rev`) are reserved for internal handler/runtime use and aren't real\n\t// fields; skip them.\n\tconst knownFields = new Set(collectionWithFields.fields.map((f) => f.slug));\n\tfor (const key of Object.keys(data)) {\n\t\tif (key.startsWith(\"_\")) continue;\n\t\tif (!knownFields.has(key)) {\n\t\t\tissues.push(`${key}: unknown field on collection '${collection}'`);\n\t\t}\n\t}\n\n\t// Zod handles type, enum, length and missing-required (in non-partial\n\t// mode) checks. Empty-string handling for required string fields is\n\t// done as a separate pass below since Zod's `z.string()` accepts \"\".\n\tconst baseSchema = generateZodSchema(collectionWithFields);\n\tconst schema = options.partial ? baseSchema.partial() : baseSchema;\n\tconst parsed = schema.safeParse(data);\n\tif (!parsed.success) {\n\t\tfor (const issue of parsed.error.issues) {\n\t\t\tissues.push(`${formatIssuePath(issue.path)}: ${issue.message}`);\n\t\t}\n\t}\n\n\t// Empty-string-on-required check. In create mode (partial=false) Zod\n\t// already catches missing/null for required fields, but `z.string()`\n\t// happily accepts \"\". In update mode (partial=true) the field is only\n\t// checked if it's present in `data`.\n\tfor (const field of collectionWithFields.fields) {\n\t\tif (!field.required) continue;\n\t\tconst present = Object.hasOwn(data, field.slug);\n\t\tif (options.partial && !present) continue;\n\t\tif (data[field.slug] === \"\") {\n\t\t\tissues.push(`${field.slug}: required (empty value not allowed)`);\n\t\t}\n\t}\n\n\t// Reference target existence. Only check fields that:\n\t// - have a value (non-missing) in `data`\n\t// - have a resolvable target collection\n\t// - in partial mode: are present in `data`\n\t// Batch one IN-query per target collection to keep round-trips low.\n\tconst refsByTarget = new Map<string, { field: string; id: string }[]>();\n\tfor (const field of collectionWithFields.fields) {\n\t\tif (field.type !== \"reference\") continue;\n\t\tif (options.partial && !Object.hasOwn(data, field.slug)) continue;\n\t\tconst value = data[field.slug];\n\t\tif (isMissing(value)) continue;\n\t\tif (typeof value !== \"string\") continue; // Zod will have flagged this already\n\t\tconst target = getReferenceTargetCollection(field);\n\t\tif (!target) continue;\n\t\tconst list = refsByTarget.get(target) ?? [];\n\t\tlist.push({ field: field.slug, id: value });\n\t\trefsByTarget.set(target, list);\n\t}\n\n\tfor (const [target, refs] of refsByTarget) {\n\t\t// Validate the target collection slug before interpolating into raw\n\t\t// SQL — defense-in-depth even though slugs are already validated at\n\t\t// schema-create time.\n\t\ttry {\n\t\t\tvalidateIdentifier(target, \"reference target collection\");\n\t\t} catch {\n\t\t\tfor (const ref of refs) {\n\t\t\t\tissues.push(`${ref.field}: invalid reference target collection '${target}'`);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst ids = [...new Set(refs.map((r) => r.id))];\n\t\tconst tableName = `ec_${target}`;\n\n\t\t// Chunk the IN clause to stay below D1's bind-parameter limit. One\n\t\t// reference per request is the common case today; chunking makes the\n\t\t// helper safe if a future multiSelect-of-references is added.\n\t\tconst found = new Set<string>();\n\t\tlet targetTableMissing = false;\n\t\tfor (const idChunk of chunks(ids, SQL_BATCH_SIZE)) {\n\t\t\ttry {\n\t\t\t\tconst rows = await sql<{ id: string }>`\n\t\t\t\t\tSELECT id FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE id IN (${sql.join(idChunk)})\n\t\t\t\t\tAND deleted_at IS NULL\n\t\t\t\t`.execute(db);\n\t\t\t\tfor (const row of rows.rows) {\n\t\t\t\t\tfound.add(row.id);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// Missing table = the target collection table doesn't exist\n\t\t\t\t// (orphan reference). Treat all those references as missing.\n\t\t\t\t// Any other DB error (permissions, connection, syntax) must\n\t\t\t\t// propagate — silently dropping data integrity errors as\n\t\t\t\t// \"not found\" is exactly the bug F5 fixes.\n\t\t\t\tif (isMissingTableError(error)) {\n\t\t\t\t\ttargetTableMissing = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tif (targetTableMissing) {\n\t\t\tfor (const ref of refs) {\n\t\t\t\tissues.push(`${ref.field}: target '${ref.id}' not found in collection '${target}'`);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const ref of refs) {\n\t\t\tif (!found.has(ref.id)) {\n\t\t\t\tissues.push(`${ref.field}: target '${ref.id}' not found in collection '${target}'`);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (issues.length === 0) return { ok: true };\n\treturn {\n\t\tok: false,\n\t\terror: {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: issues.join(\"; \"),\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validation-CwUfTdGF.mjs","names":[],"sources":["../src/api/handlers/validation.ts"],"sourcesContent":["/**\n * Field-level validation for content create / update.\n *\n * Wires the existing `generateZodSchema()` pipeline (`schema/zod-generator.ts`)\n * into the handler boundary so REST and MCP both get the same enforcement:\n *\n * - required fields must be present and non-empty\n * - select / multiSelect values must match the configured options\n * - reference fields must resolve to a real, non-trashed target\n *\n * Errors surface as `{ code: \"VALIDATION_ERROR\", message }` with all\n * offending fields listed in one message so callers can fix everything in\n * a single round trip.\n */\n\nimport { sql, type Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\nimport { SchemaRegistry } from \"../../schema/registry.js\";\nimport type { Field } from \"../../schema/types.js\";\nimport { generateZodSchema } from \"../../schema/zod-generator.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../../utils/chunks.js\";\nimport { isMissingTableError } from \"../../utils/db-errors.js\";\n\ntype ValidationResult =\n\t| { ok: true }\n\t| { ok: false; error: { code: \"VALIDATION_ERROR\" | \"COLLECTION_NOT_FOUND\"; message: string } };\n\n/** Treat `undefined`, `null`, and `\"\"` as \"not set\". */\nfunction isMissing(value: unknown): boolean {\n\treturn value === undefined || value === null || value === \"\";\n}\n\n/**\n * Resolve the target collection slug for a reference field.\n *\n * Schema-defined reference fields (the static `reference()` factory in\n * `fields/reference.ts`) put the target in `options.collection`. The MCP\n * `schema_create_field` tool also puts it there. Tests and some admin paths\n * stash it inside `validation.collection` directly; we accept both.\n */\nfunction getReferenceTargetCollection(field: Field): string | undefined {\n\tconst fromOptions = field.options?.collection;\n\tif (typeof fromOptions === \"string\" && fromOptions.length > 0) return fromOptions;\n\tconst validation = field.validation;\n\tif (validation && \"collection\" in validation) {\n\t\tconst fromValidation: unknown = (validation as { collection?: unknown }).collection;\n\t\tif (typeof fromValidation === \"string\" && fromValidation.length > 0) return fromValidation;\n\t}\n\treturn undefined;\n}\n\n/**\n * Format a Zod issue path into a human-readable field reference, e.g.\n * `tags`, `tags.1`, `image.alt`.\n */\nfunction formatIssuePath(path: ReadonlyArray<PropertyKey>): string {\n\tif (path.length === 0) return \"(root)\";\n\treturn path.map((seg) => String(seg)).join(\".\");\n}\n\n/**\n * Validate `data` against the collection's field definitions.\n *\n * `partial: true` switches Zod into partial mode so updates can include\n * only the fields being changed without tripping required-field errors on\n * fields the caller didn't touch. Required fields that ARE present in\n * partial-mode data still get the empty-string check below.\n */\nexport async function validateContentData(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tdata: Record<string, unknown>,\n\toptions: { partial?: boolean } = {},\n): Promise<ValidationResult> {\n\tconst registry = new SchemaRegistry(db);\n\tconst collectionWithFields = await registry.getCollectionWithFields(collection);\n\tif (!collectionWithFields) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: {\n\t\t\t\tcode: \"COLLECTION_NOT_FOUND\",\n\t\t\t\tmessage: `Collection '${collection}' not found`,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst issues: string[] = [];\n\n\t// Detect unknown keys explicitly so callers get a useful error rather\n\t// than silently dropped data. Leading-underscore keys (e.g. `_slug`,\n\t// `_rev`) are reserved for internal handler/runtime use and aren't real\n\t// fields; skip them.\n\tconst knownFields = new Set(collectionWithFields.fields.map((f) => f.slug));\n\tfor (const key of Object.keys(data)) {\n\t\tif (key.startsWith(\"_\")) continue;\n\t\tif (!knownFields.has(key)) {\n\t\t\tissues.push(`${key}: unknown field on collection '${collection}'`);\n\t\t}\n\t}\n\n\t// Zod handles type, enum, length and missing-required (in non-partial\n\t// mode) checks. Empty-string handling for required string fields is\n\t// done as a separate pass below since Zod's `z.string()` accepts \"\".\n\tconst baseSchema = generateZodSchema(collectionWithFields);\n\tconst schema = options.partial ? baseSchema.partial() : baseSchema;\n\tconst parsed = schema.safeParse(data);\n\tif (!parsed.success) {\n\t\tfor (const issue of parsed.error.issues) {\n\t\t\tissues.push(`${formatIssuePath(issue.path)}: ${issue.message}`);\n\t\t}\n\t}\n\n\t// Empty-string-on-required check. In create mode (partial=false) Zod\n\t// already catches missing/null for required fields, but `z.string()`\n\t// happily accepts \"\". In update mode (partial=true) the field is only\n\t// checked if it's present in `data`.\n\tfor (const field of collectionWithFields.fields) {\n\t\tif (!field.required) continue;\n\t\tconst present = Object.hasOwn(data, field.slug);\n\t\tif (options.partial && !present) continue;\n\t\tif (data[field.slug] === \"\") {\n\t\t\tissues.push(`${field.slug}: required (empty value not allowed)`);\n\t\t}\n\t}\n\n\t// Reference target existence. Only check fields that:\n\t// - have a value (non-missing) in `data`\n\t// - have a resolvable target collection\n\t// - in partial mode: are present in `data`\n\t// Batch one IN-query per target collection to keep round-trips low.\n\tconst refsByTarget = new Map<string, { field: string; id: string }[]>();\n\tfor (const field of collectionWithFields.fields) {\n\t\tif (field.type !== \"reference\") continue;\n\t\tif (options.partial && !Object.hasOwn(data, field.slug)) continue;\n\t\tconst value = data[field.slug];\n\t\tif (isMissing(value)) continue;\n\t\tif (typeof value !== \"string\") continue; // Zod will have flagged this already\n\t\tconst target = getReferenceTargetCollection(field);\n\t\tif (!target) continue;\n\t\tconst list = refsByTarget.get(target) ?? [];\n\t\tlist.push({ field: field.slug, id: value });\n\t\trefsByTarget.set(target, list);\n\t}\n\n\tfor (const [target, refs] of refsByTarget) {\n\t\t// Validate the target collection slug before interpolating into raw\n\t\t// SQL — defense-in-depth even though slugs are already validated at\n\t\t// schema-create time.\n\t\ttry {\n\t\t\tvalidateIdentifier(target, \"reference target collection\");\n\t\t} catch {\n\t\t\tfor (const ref of refs) {\n\t\t\t\tissues.push(`${ref.field}: invalid reference target collection '${target}'`);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst ids = [...new Set(refs.map((r) => r.id))];\n\t\tconst tableName = `ec_${target}`;\n\n\t\t// Chunk the IN clause to stay below D1's bind-parameter limit. One\n\t\t// reference per request is the common case today; chunking makes the\n\t\t// helper safe if a future multiSelect-of-references is added.\n\t\tconst found = new Set<string>();\n\t\tlet targetTableMissing = false;\n\t\tfor (const idChunk of chunks(ids, SQL_BATCH_SIZE)) {\n\t\t\ttry {\n\t\t\t\tconst rows = await sql<{ id: string }>`\n\t\t\t\t\tSELECT id FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE id IN (${sql.join(idChunk)})\n\t\t\t\t\tAND deleted_at IS NULL\n\t\t\t\t`.execute(db);\n\t\t\t\tfor (const row of rows.rows) {\n\t\t\t\t\tfound.add(row.id);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// Missing table = the target collection table doesn't exist\n\t\t\t\t// (orphan reference). Treat all those references as missing.\n\t\t\t\t// Any other DB error (permissions, connection, syntax) must\n\t\t\t\t// propagate — silently dropping data integrity errors as\n\t\t\t\t// \"not found\" is exactly the bug F5 fixes.\n\t\t\t\tif (isMissingTableError(error)) {\n\t\t\t\t\ttargetTableMissing = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tif (targetTableMissing) {\n\t\t\tfor (const ref of refs) {\n\t\t\t\tissues.push(`${ref.field}: target '${ref.id}' not found in collection '${target}'`);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const ref of refs) {\n\t\t\tif (!found.has(ref.id)) {\n\t\t\t\tissues.push(`${ref.field}: target '${ref.id}' not found in collection '${target}'`);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (issues.length === 0) return { ok: true };\n\treturn {\n\t\tok: false,\n\t\terror: {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: issues.join(\"; \"),\n\t\t},\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ //#region src/version.ts
2
+ const VERSION = "0.26.0";
3
+ const COMMIT = "71a148db";
4
+
5
+ //#endregion
6
+ export { VERSION as n, COMMIT as t };
7
+ //# sourceMappingURL=version-BoNBItP_.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-DHdCA24u.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["/**\n * Build-time version constants, replaced by tsdown/Vite `define`.\n * Falls back to \"dev\" when running uncompiled (tests, dev).\n */\n\ndeclare const __EMDASH_VERSION__: string;\ndeclare const __EMDASH_COMMIT__: string;\n\nexport const VERSION: string =\n\ttypeof __EMDASH_VERSION__ !== \"undefined\" ? __EMDASH_VERSION__ : \"dev\";\n\nexport const COMMIT: string = typeof __EMDASH_COMMIT__ !== \"undefined\" ? __EMDASH_COMMIT__ : \"dev\";\n"],"mappings":";AAQA,MAAa;AAGb,MAAa"}
1
+ {"version":3,"file":"version-BoNBItP_.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["/**\n * Build-time version constants, replaced by tsdown/Vite `define`.\n * Falls back to \"dev\" when running uncompiled (tests, dev).\n */\n\ndeclare const __EMDASH_VERSION__: string;\ndeclare const __EMDASH_COMMIT__: string;\n\nexport const VERSION: string =\n\ttypeof __EMDASH_VERSION__ !== \"undefined\" ? __EMDASH_VERSION__ : \"dev\";\n\nexport const COMMIT: string = typeof __EMDASH_COMMIT__ !== \"undefined\" ? __EMDASH_COMMIT__ : \"dev\";\n"],"mappings":";AAQA,MAAa;AAGb,MAAa"}
@@ -1,5 +1,5 @@
1
1
  import { r as requestCached } from "./request-cache-KCNHp_RJ.mjs";
2
- import { a as getDb } from "./loader-DrpofRon.mjs";
2
+ import { a as getDb } from "./loader-BA_zhkS_.mjs";
3
3
  import { t as getWidgetComponents$1 } from "./components-CYt4uVK9.mjs";
4
4
 
5
5
  //#region src/widgets/index.ts
@@ -106,4 +106,4 @@ function rowToWidget(row) {
106
106
 
107
107
  //#endregion
108
108
  export { rowToWidget as i, getWidgetAreas as n, getWidgetComponents as r, getWidgetArea as t };
109
- //# sourceMappingURL=widgets-C2RbFN0G.mjs.map
109
+ //# sourceMappingURL=widgets-BfOZ-ajY.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"widgets-C2RbFN0G.mjs","names":["getComponentRegistry"],"sources":["../src/widgets/index.ts"],"sourcesContent":["import { getDb } from \"../loader.js\";\nimport { requestCached } from \"../request-cache.js\";\nimport { getWidgetComponents as getComponentRegistry } from \"./components.js\";\nimport type { Widget, WidgetArea, WidgetRow, WidgetComponentDef } from \"./types.js\";\n\nexport type {\n\tWidget,\n\tWidgetArea,\n\tWidgetType,\n\tWidgetComponentDef,\n\tPropDef,\n\tCreateWidgetAreaInput,\n\tCreateWidgetInput,\n\tUpdateWidgetInput,\n\tReorderWidgetsInput,\n} from \"./types.js\";\n\n/**\n * Get a widget area by name, with all its widgets.\n *\n * Single query with a left join rather than area-then-widgets so the\n * common case costs one round-trip. An area with no widgets yields one\n * row with null widget columns, which we skip when mapping.\n */\nexport async function getWidgetArea(name: string): Promise<WidgetArea | null> {\n\treturn requestCached(`widget-area:${name}`, async () => {\n\t\tconst db = await getDb();\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_widget_areas as a\")\n\t\t\t.leftJoin(\"_emdash_widgets as w\", \"w.area_id\", \"a.id\")\n\t\t\t.select([\n\t\t\t\t\"a.id as a_id\",\n\t\t\t\t\"a.name as a_name\",\n\t\t\t\t\"a.label as a_label\",\n\t\t\t\t\"a.description as a_description\",\n\t\t\t\t\"w.id as w_id\",\n\t\t\t\t\"w.type as w_type\",\n\t\t\t\t\"w.title as w_title\",\n\t\t\t\t\"w.content as w_content\",\n\t\t\t\t\"w.menu_name as w_menu_name\",\n\t\t\t\t\"w.component_id as w_component_id\",\n\t\t\t\t\"w.component_props as w_component_props\",\n\t\t\t\t\"w.area_id as w_area_id\",\n\t\t\t\t\"w.sort_order as w_sort_order\",\n\t\t\t\t\"w.created_at as w_created_at\",\n\t\t\t])\n\t\t\t.where(\"a.name\", \"=\", name)\n\t\t\t.orderBy(\"w.sort_order\", \"asc\")\n\t\t\t.execute();\n\n\t\tconst first = rows[0];\n\t\tif (!first) return null;\n\t\tconst widgets: Widget[] = [];\n\t\tfor (const row of rows) {\n\t\t\tif (row.w_id === null) continue; // area has no widgets (left-join null row)\n\t\t\t// Left-join makes every w_* column nullable in the type; at runtime\n\t\t\t// they're all non-null once w_id is (we match on widgets.area_id, so\n\t\t\t// a widget row always has the not-null columns filled). Cast is the\n\t\t\t// price of that structural fact.\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- left-join row is non-null when w_id is set; see above\n\t\t\tconst widgetRow = {\n\t\t\t\tid: row.w_id,\n\t\t\t\ttype: row.w_type,\n\t\t\t\ttitle: row.w_title,\n\t\t\t\tcontent: row.w_content,\n\t\t\t\tmenu_name: row.w_menu_name,\n\t\t\t\tcomponent_id: row.w_component_id,\n\t\t\t\tcomponent_props: row.w_component_props,\n\t\t\t\tarea_id: row.w_area_id,\n\t\t\t\tsort_order: row.w_sort_order,\n\t\t\t\tcreated_at: row.w_created_at,\n\t\t\t} as WidgetRow;\n\t\t\twidgets.push(rowToWidget(widgetRow));\n\t\t}\n\n\t\treturn {\n\t\t\tid: first.a_id,\n\t\t\tname: first.a_name,\n\t\t\tlabel: first.a_label,\n\t\t\tdescription: first.a_description ?? undefined,\n\t\t\twidgets,\n\t\t};\n\t});\n}\n\n/**\n * Get all widget areas with their widgets\n */\nexport async function getWidgetAreas(): Promise<WidgetArea[]> {\n\tconst db = await getDb();\n\t// Get all areas\n\tconst areaRows = await db.selectFrom(\"_emdash_widget_areas\").selectAll().execute();\n\n\t// Get all widgets\n\tconst widgetRows = await db\n\t\t.selectFrom(\"_emdash_widgets\")\n\t\t.selectAll()\n\t\t.$castTo<WidgetRow>()\n\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t.execute();\n\n\t// Group widgets by area\n\tconst widgetsByArea = new Map<string, Widget[]>();\n\tfor (const row of widgetRows) {\n\t\tif (!widgetsByArea.has(row.area_id)) {\n\t\t\twidgetsByArea.set(row.area_id, []);\n\t\t}\n\t\twidgetsByArea.get(row.area_id)!.push(rowToWidget(row));\n\t}\n\n\t// Combine\n\treturn areaRows.map((areaRow) => ({\n\t\tid: areaRow.id,\n\t\tname: areaRow.name,\n\t\tlabel: areaRow.label,\n\t\tdescription: areaRow.description ?? undefined,\n\t\twidgets: widgetsByArea.get(areaRow.id) || [],\n\t}));\n}\n\n/**\n * Get available widget components (for admin UI)\n */\nexport function getWidgetComponents(): WidgetComponentDef[] {\n\treturn getComponentRegistry();\n}\n\n/**\n * Convert a widget row to the API type\n */\nexport function rowToWidget(row: WidgetRow): Widget {\n\tconst widget: Widget = {\n\t\tid: row.id,\n\t\ttype: row.type,\n\t\ttitle: row.title ?? undefined,\n\t};\n\n\t// Type-specific fields\n\tif (row.type === \"content\" && row.content) {\n\t\ttry {\n\t\t\twidget.content = JSON.parse(row.content);\n\t\t} catch {\n\t\t\t// Invalid JSON, ignore\n\t\t}\n\t}\n\n\tif (row.type === \"menu\" && row.menu_name) {\n\t\twidget.menuName = row.menu_name;\n\t}\n\n\tif (row.type === \"component\" && row.component_id) {\n\t\twidget.componentId = row.component_id;\n\t\tif (row.component_props) {\n\t\t\ttry {\n\t\t\t\twidget.componentProps = JSON.parse(row.component_props);\n\t\t\t} catch {\n\t\t\t\t// Invalid JSON, ignore\n\t\t\t}\n\t\t}\n\t}\n\n\treturn widget;\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,eAAsB,cAAc,MAA0C;AAC7E,QAAO,cAAc,eAAe,QAAQ,YAAY;EAEvD,MAAM,OAAO,OADF,MAAM,OAAO,EAEtB,WAAW,4BAA4B,CACvC,SAAS,wBAAwB,aAAa,OAAO,CACrD,OAAO;GACP;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,CAAC,CACD,MAAM,UAAU,KAAK,KAAK,CAC1B,QAAQ,gBAAgB,MAAM,CAC9B,SAAS;EAEX,MAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,MAAO,QAAO;EACnB,MAAM,UAAoB,EAAE;AAC5B,OAAK,MAAM,OAAO,MAAM;AACvB,OAAI,IAAI,SAAS,KAAM;GAMvB,MAAM,YAAY;IACjB,IAAI,IAAI;IACR,MAAM,IAAI;IACV,OAAO,IAAI;IACX,SAAS,IAAI;IACb,WAAW,IAAI;IACf,cAAc,IAAI;IAClB,iBAAiB,IAAI;IACrB,SAAS,IAAI;IACb,YAAY,IAAI;IAChB,YAAY,IAAI;IAChB;AACD,WAAQ,KAAK,YAAY,UAAU,CAAC;;AAGrC,SAAO;GACN,IAAI,MAAM;GACV,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,aAAa,MAAM,iBAAiB;GACpC;GACA;GACA;;;;;AAMH,eAAsB,iBAAwC;CAC7D,MAAM,KAAK,MAAM,OAAO;CAExB,MAAM,WAAW,MAAM,GAAG,WAAW,uBAAuB,CAAC,WAAW,CAAC,SAAS;CAGlF,MAAM,aAAa,MAAM,GACvB,WAAW,kBAAkB,CAC7B,WAAW,CACX,SAAoB,CACpB,QAAQ,cAAc,MAAM,CAC5B,SAAS;CAGX,MAAM,gCAAgB,IAAI,KAAuB;AACjD,MAAK,MAAM,OAAO,YAAY;AAC7B,MAAI,CAAC,cAAc,IAAI,IAAI,QAAQ,CAClC,eAAc,IAAI,IAAI,SAAS,EAAE,CAAC;AAEnC,gBAAc,IAAI,IAAI,QAAQ,CAAE,KAAK,YAAY,IAAI,CAAC;;AAIvD,QAAO,SAAS,KAAK,aAAa;EACjC,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,OAAO,QAAQ;EACf,aAAa,QAAQ,eAAe;EACpC,SAAS,cAAc,IAAI,QAAQ,GAAG,IAAI,EAAE;EAC5C,EAAE;;;;;AAMJ,SAAgB,sBAA4C;AAC3D,QAAOA,uBAAsB;;;;;AAM9B,SAAgB,YAAY,KAAwB;CACnD,MAAM,SAAiB;EACtB,IAAI,IAAI;EACR,MAAM,IAAI;EACV,OAAO,IAAI,SAAS;EACpB;AAGD,KAAI,IAAI,SAAS,aAAa,IAAI,QACjC,KAAI;AACH,SAAO,UAAU,KAAK,MAAM,IAAI,QAAQ;SACjC;AAKT,KAAI,IAAI,SAAS,UAAU,IAAI,UAC9B,QAAO,WAAW,IAAI;AAGvB,KAAI,IAAI,SAAS,eAAe,IAAI,cAAc;AACjD,SAAO,cAAc,IAAI;AACzB,MAAI,IAAI,gBACP,KAAI;AACH,UAAO,iBAAiB,KAAK,MAAM,IAAI,gBAAgB;UAChD;;AAMV,QAAO"}
1
+ {"version":3,"file":"widgets-BfOZ-ajY.mjs","names":["getComponentRegistry"],"sources":["../src/widgets/index.ts"],"sourcesContent":["import { getDb } from \"../loader.js\";\nimport { requestCached } from \"../request-cache.js\";\nimport { getWidgetComponents as getComponentRegistry } from \"./components.js\";\nimport type { Widget, WidgetArea, WidgetRow, WidgetComponentDef } from \"./types.js\";\n\nexport type {\n\tWidget,\n\tWidgetArea,\n\tWidgetType,\n\tWidgetComponentDef,\n\tPropDef,\n\tCreateWidgetAreaInput,\n\tCreateWidgetInput,\n\tUpdateWidgetInput,\n\tReorderWidgetsInput,\n} from \"./types.js\";\n\n/**\n * Get a widget area by name, with all its widgets.\n *\n * Single query with a left join rather than area-then-widgets so the\n * common case costs one round-trip. An area with no widgets yields one\n * row with null widget columns, which we skip when mapping.\n */\nexport async function getWidgetArea(name: string): Promise<WidgetArea | null> {\n\treturn requestCached(`widget-area:${name}`, async () => {\n\t\tconst db = await getDb();\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_widget_areas as a\")\n\t\t\t.leftJoin(\"_emdash_widgets as w\", \"w.area_id\", \"a.id\")\n\t\t\t.select([\n\t\t\t\t\"a.id as a_id\",\n\t\t\t\t\"a.name as a_name\",\n\t\t\t\t\"a.label as a_label\",\n\t\t\t\t\"a.description as a_description\",\n\t\t\t\t\"w.id as w_id\",\n\t\t\t\t\"w.type as w_type\",\n\t\t\t\t\"w.title as w_title\",\n\t\t\t\t\"w.content as w_content\",\n\t\t\t\t\"w.menu_name as w_menu_name\",\n\t\t\t\t\"w.component_id as w_component_id\",\n\t\t\t\t\"w.component_props as w_component_props\",\n\t\t\t\t\"w.area_id as w_area_id\",\n\t\t\t\t\"w.sort_order as w_sort_order\",\n\t\t\t\t\"w.created_at as w_created_at\",\n\t\t\t])\n\t\t\t.where(\"a.name\", \"=\", name)\n\t\t\t.orderBy(\"w.sort_order\", \"asc\")\n\t\t\t.execute();\n\n\t\tconst first = rows[0];\n\t\tif (!first) return null;\n\t\tconst widgets: Widget[] = [];\n\t\tfor (const row of rows) {\n\t\t\tif (row.w_id === null) continue; // area has no widgets (left-join null row)\n\t\t\t// Left-join makes every w_* column nullable in the type; at runtime\n\t\t\t// they're all non-null once w_id is (we match on widgets.area_id, so\n\t\t\t// a widget row always has the not-null columns filled). Cast is the\n\t\t\t// price of that structural fact.\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- left-join row is non-null when w_id is set; see above\n\t\t\tconst widgetRow = {\n\t\t\t\tid: row.w_id,\n\t\t\t\ttype: row.w_type,\n\t\t\t\ttitle: row.w_title,\n\t\t\t\tcontent: row.w_content,\n\t\t\t\tmenu_name: row.w_menu_name,\n\t\t\t\tcomponent_id: row.w_component_id,\n\t\t\t\tcomponent_props: row.w_component_props,\n\t\t\t\tarea_id: row.w_area_id,\n\t\t\t\tsort_order: row.w_sort_order,\n\t\t\t\tcreated_at: row.w_created_at,\n\t\t\t} as WidgetRow;\n\t\t\twidgets.push(rowToWidget(widgetRow));\n\t\t}\n\n\t\treturn {\n\t\t\tid: first.a_id,\n\t\t\tname: first.a_name,\n\t\t\tlabel: first.a_label,\n\t\t\tdescription: first.a_description ?? undefined,\n\t\t\twidgets,\n\t\t};\n\t});\n}\n\n/**\n * Get all widget areas with their widgets\n */\nexport async function getWidgetAreas(): Promise<WidgetArea[]> {\n\tconst db = await getDb();\n\t// Get all areas\n\tconst areaRows = await db.selectFrom(\"_emdash_widget_areas\").selectAll().execute();\n\n\t// Get all widgets\n\tconst widgetRows = await db\n\t\t.selectFrom(\"_emdash_widgets\")\n\t\t.selectAll()\n\t\t.$castTo<WidgetRow>()\n\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t.execute();\n\n\t// Group widgets by area\n\tconst widgetsByArea = new Map<string, Widget[]>();\n\tfor (const row of widgetRows) {\n\t\tif (!widgetsByArea.has(row.area_id)) {\n\t\t\twidgetsByArea.set(row.area_id, []);\n\t\t}\n\t\twidgetsByArea.get(row.area_id)!.push(rowToWidget(row));\n\t}\n\n\t// Combine\n\treturn areaRows.map((areaRow) => ({\n\t\tid: areaRow.id,\n\t\tname: areaRow.name,\n\t\tlabel: areaRow.label,\n\t\tdescription: areaRow.description ?? undefined,\n\t\twidgets: widgetsByArea.get(areaRow.id) || [],\n\t}));\n}\n\n/**\n * Get available widget components (for admin UI)\n */\nexport function getWidgetComponents(): WidgetComponentDef[] {\n\treturn getComponentRegistry();\n}\n\n/**\n * Convert a widget row to the API type\n */\nexport function rowToWidget(row: WidgetRow): Widget {\n\tconst widget: Widget = {\n\t\tid: row.id,\n\t\ttype: row.type,\n\t\ttitle: row.title ?? undefined,\n\t};\n\n\t// Type-specific fields\n\tif (row.type === \"content\" && row.content) {\n\t\ttry {\n\t\t\twidget.content = JSON.parse(row.content);\n\t\t} catch {\n\t\t\t// Invalid JSON, ignore\n\t\t}\n\t}\n\n\tif (row.type === \"menu\" && row.menu_name) {\n\t\twidget.menuName = row.menu_name;\n\t}\n\n\tif (row.type === \"component\" && row.component_id) {\n\t\twidget.componentId = row.component_id;\n\t\tif (row.component_props) {\n\t\t\ttry {\n\t\t\t\twidget.componentProps = JSON.parse(row.component_props);\n\t\t\t} catch {\n\t\t\t\t// Invalid JSON, ignore\n\t\t\t}\n\t\t}\n\t}\n\n\treturn widget;\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,eAAsB,cAAc,MAA0C;AAC7E,QAAO,cAAc,eAAe,QAAQ,YAAY;EAEvD,MAAM,OAAO,OADF,MAAM,OAAO,EAEtB,WAAW,4BAA4B,CACvC,SAAS,wBAAwB,aAAa,OAAO,CACrD,OAAO;GACP;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,CAAC,CACD,MAAM,UAAU,KAAK,KAAK,CAC1B,QAAQ,gBAAgB,MAAM,CAC9B,SAAS;EAEX,MAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,MAAO,QAAO;EACnB,MAAM,UAAoB,EAAE;AAC5B,OAAK,MAAM,OAAO,MAAM;AACvB,OAAI,IAAI,SAAS,KAAM;GAMvB,MAAM,YAAY;IACjB,IAAI,IAAI;IACR,MAAM,IAAI;IACV,OAAO,IAAI;IACX,SAAS,IAAI;IACb,WAAW,IAAI;IACf,cAAc,IAAI;IAClB,iBAAiB,IAAI;IACrB,SAAS,IAAI;IACb,YAAY,IAAI;IAChB,YAAY,IAAI;IAChB;AACD,WAAQ,KAAK,YAAY,UAAU,CAAC;;AAGrC,SAAO;GACN,IAAI,MAAM;GACV,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,aAAa,MAAM,iBAAiB;GACpC;GACA;GACA;;;;;AAMH,eAAsB,iBAAwC;CAC7D,MAAM,KAAK,MAAM,OAAO;CAExB,MAAM,WAAW,MAAM,GAAG,WAAW,uBAAuB,CAAC,WAAW,CAAC,SAAS;CAGlF,MAAM,aAAa,MAAM,GACvB,WAAW,kBAAkB,CAC7B,WAAW,CACX,SAAoB,CACpB,QAAQ,cAAc,MAAM,CAC5B,SAAS;CAGX,MAAM,gCAAgB,IAAI,KAAuB;AACjD,MAAK,MAAM,OAAO,YAAY;AAC7B,MAAI,CAAC,cAAc,IAAI,IAAI,QAAQ,CAClC,eAAc,IAAI,IAAI,SAAS,EAAE,CAAC;AAEnC,gBAAc,IAAI,IAAI,QAAQ,CAAE,KAAK,YAAY,IAAI,CAAC;;AAIvD,QAAO,SAAS,KAAK,aAAa;EACjC,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,OAAO,QAAQ;EACf,aAAa,QAAQ,eAAe;EACpC,SAAS,cAAc,IAAI,QAAQ,GAAG,IAAI,EAAE;EAC5C,EAAE;;;;;AAMJ,SAAgB,sBAA4C;AAC3D,QAAOA,uBAAsB;;;;;AAM9B,SAAgB,YAAY,KAAwB;CACnD,MAAM,SAAiB;EACtB,IAAI,IAAI;EACR,MAAM,IAAI;EACV,OAAO,IAAI,SAAS;EACpB;AAGD,KAAI,IAAI,SAAS,aAAa,IAAI,QACjC,KAAI;AACH,SAAO,UAAU,KAAK,MAAM,IAAI,QAAQ;SACjC;AAKT,KAAI,IAAI,SAAS,UAAU,IAAI,UAC9B,QAAO,WAAW,IAAI;AAGvB,KAAI,IAAI,SAAS,eAAe,IAAI,cAAc;AACjD,SAAO,cAAc,IAAI;AACzB,MAAI,IAAI,gBACP,KAAI;AACH,UAAO,iBAAiB,KAAK,MAAM,IAAI,gBAAgB;UAChD;;AAMV,QAAO"}
@@ -1,4 +1,4 @@
1
- import { a as __exportAll } from "./runner-BsquExlK.mjs";
1
+ import { a as __exportAll } from "./runner-DPdt_Wpa.mjs";
2
2
  import { n as hashString } from "./hash-DrvzIXcz.mjs";
3
3
  import { z } from "zod";
4
4
 
@@ -46,7 +46,10 @@ function getBaseSchema(type, field) {
46
46
  case "number": return z.number();
47
47
  case "integer": return z.number().int();
48
48
  case "boolean": return z.preprocess((v) => v === 0 || v === 1 ? Boolean(v) : v, z.boolean());
49
- case "datetime": return z.string().datetime().or(z.string().date());
49
+ case "datetime": return z.iso.datetime({
50
+ offset: true,
51
+ local: true
52
+ }).or(z.iso.date());
50
53
  case "select": {
51
54
  const options = field.validation?.options;
52
55
  if (options && options.length > 0) {
@@ -268,4 +271,4 @@ function uniqueInterfaceNames(collections) {
268
271
 
269
272
  //#endregion
270
273
  export { zod_generator_exports as i, generateZodSchema as n, uniqueInterfaceNames as r, generateTypeScript as t };
271
- //# sourceMappingURL=zod-generator-xEHODMJf.mjs.map
274
+ //# sourceMappingURL=zod-generator-gFsRuNNy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-generator-gFsRuNNy.mjs","names":[],"sources":["../src/schema/zod-generator.ts"],"sourcesContent":["import { z, type ZodTypeAny } from \"zod\";\n\nimport { hashString } from \"../utils/hash.js\";\nimport type { Field, FieldType, CollectionWithFields } from \"./types.js\";\n\n/** Pattern to split on underscores, hyphens, and spaces for PascalCase conversion */\nconst PASCAL_CASE_SPLIT_PATTERN = /[_\\-\\s]+/;\n\n/**\n * Generate a Zod schema from a collection's field definitions\n *\n * This allows runtime validation of content based on dynamically\n * defined schemas stored in D1.\n */\nexport function generateZodSchema(\n\tcollection: CollectionWithFields,\n): z.ZodObject<Record<string, ZodTypeAny>> {\n\tconst shape: Record<string, ZodTypeAny> = {};\n\n\tfor (const field of collection.fields) {\n\t\tshape[field.slug] = generateFieldSchema(field);\n\t}\n\n\treturn z.object(shape);\n}\n\n/**\n * Generate Zod schema for a single field\n */\nexport function generateFieldSchema(field: Field): ZodTypeAny {\n\tlet schema = getBaseSchema(field.type, field);\n\n\t// Apply validation rules\n\tif (field.validation) {\n\t\tschema = applyValidation(schema, field);\n\t}\n\n\t// Apply required/optional. Non-required fields use `.nullish()` rather\n\t// than `.optional()` because the underlying SQLite columns are nullable\n\t// (see `SchemaRegistry.addFieldColumn` -- non-required fields are added\n\t// without `NOT NULL`). The admin re-sends what it loaded from the\n\t// server on autosave, so any field that's actually `null` in the DB\n\t// must round-trip cleanly through the validator. `.optional()` only\n\t// accepts `undefined`; `.nullish()` accepts both `undefined` and\n\t// `null`. (#867 — autosave failures on seeded entries.)\n\tif (!field.required) {\n\t\tschema = schema.nullish();\n\t}\n\n\t// Apply default value\n\tif (field.defaultValue !== undefined) {\n\t\tschema = schema.default(field.defaultValue);\n\t}\n\n\treturn schema;\n}\n\n/**\n * Get base Zod schema for a field type\n */\nfunction getBaseSchema(type: FieldType, field: Field): ZodTypeAny {\n\tswitch (type) {\n\t\tcase \"url\":\n\t\t\treturn z.string().url();\n\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\t\treturn z.string();\n\n\t\tcase \"number\":\n\t\t\treturn z.number();\n\n\t\tcase \"integer\":\n\t\t\treturn z.number().int();\n\n\t\tcase \"boolean\":\n\t\t\t// Boolean fields map to `INTEGER` columns (`FIELD_TYPE_TO_COLUMN`\n\t\t\t// in `schema/types.ts`) and `serializeValue` in\n\t\t\t// `database/repositories/content.ts` writes booleans as 0/1.\n\t\t\t// `deserializeValue` never converts them back, so reads return\n\t\t\t// numbers. Coerce the stored 0/1 shape here so a GET → POST\n\t\t\t// round-trip on a boolean field passes validation. Other inputs\n\t\t\t// (strings, other numbers) fall through to `z.boolean()` and\n\t\t\t// produce its standard rejection.\n\t\t\treturn z.preprocess((v) => (v === 0 || v === 1 ? Boolean(v) : v), z.boolean());\n\n\t\tcase \"datetime\":\n\t\t\t// Accept every value that legitimately round-trips through the admin\n\t\t\t// and seeds: ISO with `Z`, ISO with a timezone offset, a naive\n\t\t\t// datetime (`YYYY-MM-DDTHH:mm[:ss]` -- what `<input type=\"datetime-local\">`\n\t\t\t// and many seeds produce), and a date-only value. The admin re-sends\n\t\t\t// every loaded field on autosave, so a stored naive datetime must\n\t\t\t// validate or the entry becomes unsavable through its own editor\n\t\t\t// (#1368; same class as #867). `z.iso.*` retains semantic validation,\n\t\t\t// so impossible dates are still rejected.\n\t\t\treturn z.iso.datetime({ offset: true, local: true }).or(z.iso.date());\n\n\t\tcase \"select\": {\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\tconst [first, ...rest] = options;\n\t\t\t\treturn z.enum([first, ...rest]);\n\t\t\t}\n\t\t\treturn z.string();\n\t\t}\n\n\t\tcase \"multiSelect\": {\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\tconst [first, ...rest] = multiOptions;\n\t\t\t\treturn z.array(z.enum([first, ...rest]));\n\t\t\t}\n\t\t\treturn z.array(z.string());\n\t\t}\n\n\t\tcase \"portableText\":\n\t\t\t// Portable Text is an array of blocks. We require `_type` because\n\t\t\t// renderers dispatch on it, but `_key` is intentionally optional:\n\t\t\t// it's a UI-layer concern that the editor regenerates on every\n\t\t\t// change (see `PortableTextEditor`), and the rest of this schema\n\t\t\t// uses `.passthrough()` for everything below the top level. Making\n\t\t\t// `_key` strictly required here was an accidentally tight invariant\n\t\t\t// that rejected any seed/import data not authored against the\n\t\t\t// editor (#867 — autosave failures on seeded template content).\n\t\t\treturn z.array(\n\t\t\t\tz\n\t\t\t\t\t.object({\n\t\t\t\t\t\t_type: z.string(),\n\t\t\t\t\t\t_key: z.string().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.passthrough(),\n\t\t\t);\n\n\t\tcase \"image\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\talt: z.string().optional(),\n\t\t\t\twidth: z.number().optional(),\n\t\t\t\theight: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"cloudflare-images\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Admin-side preview URL for external providers (not persisted by plugins) */\n\t\t\t\tpreviewUrl: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"file\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\tfilename: z.string().optional(),\n\t\t\t\tmimeType: z.string().optional(),\n\t\t\t\tsize: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"s3\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"reference\":\n\t\t\treturn z.string(); // Reference ID\n\n\t\tcase \"json\":\n\t\t\treturn z.unknown();\n\n\t\tdefault:\n\t\t\treturn z.unknown();\n\t}\n}\n\n/**\n * Apply validation rules to a schema\n */\nfunction applyValidation(schema: ZodTypeAny, field: Field): ZodTypeAny {\n\tconst validation = field.validation;\n\tif (!validation) return schema;\n\n\t// String validations\n\tif (schema instanceof z.ZodString) {\n\t\tlet strSchema = schema;\n\t\tif (validation.minLength !== undefined) {\n\t\t\tstrSchema = strSchema.min(validation.minLength);\n\t\t}\n\t\tif (validation.maxLength !== undefined) {\n\t\t\tstrSchema = strSchema.max(validation.maxLength);\n\t\t}\n\t\tif (validation.pattern) {\n\t\t\tstrSchema = strSchema.regex(new RegExp(validation.pattern));\n\t\t}\n\t\treturn strSchema;\n\t}\n\n\t// Number validations\n\tif (schema instanceof z.ZodNumber) {\n\t\tlet numSchema = schema;\n\t\tif (validation.min !== undefined) {\n\t\t\tnumSchema = numSchema.min(validation.min);\n\t\t}\n\t\tif (validation.max !== undefined) {\n\t\t\tnumSchema = numSchema.max(validation.max);\n\t\t}\n\t\treturn numSchema;\n\t}\n\n\treturn schema;\n}\n\n/**\n * Schema cache to avoid regenerating schemas on every request\n */\nconst schemaCache = new Map<string, { schema: z.ZodObject<any>; version: string }>();\n\n/**\n * Get or generate a cached schema for a collection\n */\nexport function getCachedSchema(\n\tcollection: CollectionWithFields,\n\tversion?: string,\n): z.ZodObject<any> {\n\tconst cacheKey = collection.slug;\n\tconst cached = schemaCache.get(cacheKey);\n\n\t// If version matches, return cached schema\n\tif (cached && (!version || cached.version === version)) {\n\t\treturn cached.schema;\n\t}\n\n\t// Generate new schema\n\tconst schema = generateZodSchema(collection);\n\n\t// Cache it\n\tschemaCache.set(cacheKey, {\n\t\tschema,\n\t\tversion: version || collection.updatedAt,\n\t});\n\n\treturn schema;\n}\n\n/**\n * Invalidate cached schema for a collection\n */\nexport function invalidateSchemaCache(slug: string): void {\n\tschemaCache.delete(slug);\n}\n\n/**\n * Clear all cached schemas\n */\nexport function clearSchemaCache(): void {\n\tschemaCache.clear();\n}\n\n/**\n * Validate data against a collection's schema\n */\nexport function validateContent(\n\tcollection: CollectionWithFields,\n\tdata: unknown,\n): { success: true; data: unknown } | { success: false; errors: z.ZodError } {\n\tconst schema = getCachedSchema(collection);\n\n\tconst result = schema.safeParse(data);\n\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\n\treturn { success: false, errors: result.error };\n}\n\n/**\n * Generate TypeScript interface from field definitions\n * Used by CLI `emdash types` to generate types\n */\nexport function generateTypeScript(\n\tcollection: CollectionWithFields,\n\tinterfaceName: string = getInterfaceName(collection),\n): string {\n\tconst lines: string[] = [];\n\n\tlines.push(`export interface ${interfaceName} {`);\n\tlines.push(` id: string;`);\n\tlines.push(` slug: string | null;`);\n\tlines.push(` status: string;`);\n\n\tfor (const field of collection.fields) {\n\t\tconst tsType = fieldTypeToTypeScript(field);\n\t\tconst optional = field.required ? \"\" : \"?\";\n\t\tlines.push(` ${field.slug}${optional}: ${tsType};`);\n\t}\n\n\tlines.push(` createdAt: Date;`);\n\tlines.push(` updatedAt: Date;`);\n\tlines.push(` publishedAt: Date | null;`);\n\t// Bylines are eagerly loaded by getEmDashCollection/getEmDashEntry\n\tlines.push(` bylines?: ContentBylineCredit[];`);\n\t// Taxonomy terms are eagerly loaded by getEmDashCollection/getEmDashEntry,\n\t// keyed by taxonomy name (e.g. data.terms?.tag)\n\tlines.push(` terms?: Record<string, TaxonomyTerm[]>;`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate a complete types file with module augmentation\n * This produces emdash-env.d.ts content that provides typed query functions\n */\nexport function generateTypesFile(collections: CollectionWithFields[]): string {\n\tconst lines: string[] = [];\n\n\t// Header\n\tlines.push(`// Generated by EmDash on dev server start`);\n\tlines.push(`// Do not edit manually`);\n\tlines.push(``);\n\tlines.push(`/// <reference types=\"emdash/locals\" />`);\n\tlines.push(``);\n\n\t// Check if we need PortableTextBlock import\n\tconst needsPortableText = collections.some((c) =>\n\t\tc.fields.some((f) => f.type === \"portableText\"),\n\t);\n\n\t// Build imports - ContentBylineCredit and TaxonomyTerm are always needed\n\t// for the hydrated bylines/terms fields\n\tconst imports = [\"ContentBylineCredit\", \"TaxonomyTerm\"];\n\tif (needsPortableText) {\n\t\timports.push(\"PortableTextBlock\");\n\t}\n\tlines.push(`import type { ${imports.join(\", \")} } from \"emdash\";`);\n\tlines.push(``);\n\n\t// Singularizing the slug can map two distinct slugs to the same name\n\t// (e.g. `book` and `books` both -> `Book`), so resolve collisions up front\n\t// to keep every interface identifier unique within the file.\n\tconst interfaceNames = uniqueInterfaceNames(collections);\n\n\t// Generate individual interfaces\n\tfor (const collection of collections) {\n\t\tlines.push(generateTypeScript(collection, interfaceNames.get(collection.slug)));\n\t\tlines.push(``);\n\t}\n\n\t// Generate the Collections interface for module augmentation\n\tlines.push(`declare module \"emdash\" {`);\n\tlines.push(` interface EmDashCollections {`);\n\tfor (const collection of collections) {\n\t\tlines.push(` ${collection.slug}: ${interfaceNames.get(collection.slug)};`);\n\t}\n\tlines.push(` }`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate schema hash for cache invalidation\n */\nexport async function generateSchemaHash(collections: CollectionWithFields[]): Promise<string> {\n\tconst str = JSON.stringify(\n\t\tcollections.map((c) => ({\n\t\t\tslug: c.slug,\n\t\t\tfields: c.fields.map((f) => ({\n\t\t\t\tslug: f.slug,\n\t\t\t\ttype: f.type,\n\t\t\t\trequired: f.required,\n\t\t\t\tvalidation: f.validation,\n\t\t\t})),\n\t\t})),\n\t);\n\treturn hashString(str);\n}\n\n/**\n * Map field type to TypeScript type\n */\nfunction fieldTypeToTypeScript(field: Field): string {\n\tswitch (field.type) {\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\tcase \"url\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\n\t\tcase \"number\":\n\t\tcase \"integer\":\n\t\t\treturn \"number\";\n\n\t\tcase \"boolean\":\n\t\t\treturn \"boolean\";\n\n\t\tcase \"select\":\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\treturn options.map((o) => `\"${o}\"`).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\n\t\tcase \"multiSelect\":\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\treturn `(${multiOptions.map((o) => `\"${o}\"`).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\n\t\tcase \"portableText\":\n\t\t\treturn \"PortableTextBlock[]\";\n\n\t\tcase \"image\":\n\t\t\treturn \"{ id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"file\":\n\t\t\treturn \"{ id: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"reference\":\n\t\t\t// Could be enhanced to include the referenced collection type\n\t\t\treturn \"string\";\n\n\t\tcase \"json\":\n\t\t\treturn \"unknown\";\n\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Convert string to PascalCase (handles slugs, spaces, etc.)\n */\nfunction pascalCase(str: string): string {\n\treturn str\n\t\t.split(PASCAL_CASE_SPLIT_PATTERN)\n\t\t.filter(Boolean)\n\t\t.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(\"\");\n}\n\n/**\n * Naive singularization for slug-derived interface names. Handles the common\n * English plural endings; intentionally simple, not a full inflector.\n */\nfunction singularize(str: string): string {\n\tif (str.endsWith(\"ies\")) {\n\t\treturn str.slice(0, -3) + \"y\";\n\t}\n\tif (\n\t\tstr.endsWith(\"es\") &&\n\t\t(str.endsWith(\"sses\") || str.endsWith(\"xes\") || str.endsWith(\"ches\") || str.endsWith(\"shes\"))\n\t) {\n\t\treturn str.slice(0, -2);\n\t}\n\tif (str.endsWith(\"s\") && !str.endsWith(\"ss\")) {\n\t\treturn str.slice(0, -1);\n\t}\n\treturn str;\n}\n\n/**\n * Get the interface name for a collection.\n *\n * Derived from the slug, not the human label. Slugs are constrained to\n * `/^[a-z][a-z0-9_]*$/`, so PascalCasing one always yields a valid TS\n * identifier; labels are arbitrary and user-controlled (punctuation, spaces,\n * duplicates across collections), which produced syntactically invalid or\n * duplicate interface names. The slug is singularized first because the\n * interface describes a single entry, not the collection (`posts` -> `Post`).\n *\n * Singularization can map two distinct slugs onto the same name, so callers\n * generating more than one interface must dedupe -- see `uniqueInterfaceNames`.\n */\nfunction getInterfaceName(collection: CollectionWithFields): string {\n\treturn pascalCase(singularize(collection.slug));\n}\n\n/**\n * Resolve interface names for a set of collections, guaranteeing each is\n * unique within the file. Collisions (from singularization or PascalCasing\n * collapsing distinct slugs) get a numeric suffix in collection order, so the\n * generated `.d.ts` never declares two interfaces with the same identifier.\n *\n * The suffix is chosen against the set of names already emitted, not a\n * per-base counter, so a generated name can't collide with another slug's\n * base name (e.g. slugs `book`, `books`, `book2`: `books` -> `Book2` would\n * clash with `book2`, so it advances to `Book3`).\n */\nexport function uniqueInterfaceNames(collections: CollectionWithFields[]): Map<string, string> {\n\tconst used = new Set<string>();\n\tconst names = new Map<string, string>();\n\tfor (const collection of collections) {\n\t\tconst base = getInterfaceName(collection);\n\t\tlet name = base;\n\t\tlet suffix = 2;\n\t\twhile (used.has(name)) {\n\t\t\tname = `${base}${suffix}`;\n\t\t\tsuffix++;\n\t\t}\n\t\tused.add(name);\n\t\tnames.set(collection.slug, name);\n\t}\n\treturn names;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emdash",
3
- "version": "0.25.1",
3
+ "version": "0.26.0",
4
4
  "description": "Astro-native CMS with WordPress migration support",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -230,9 +230,9 @@
230
230
  "upng-js": "^2.1.0",
231
231
  "zod": "^4.4.1",
232
232
  "@atcute/client": "^5.0.0",
233
- "@emdash-cms/admin": "0.25.1",
234
- "@emdash-cms/auth": "0.25.1",
235
- "@emdash-cms/gutenberg-to-portable-text": "0.25.1",
233
+ "@emdash-cms/admin": "0.26.0",
234
+ "@emdash-cms/auth": "0.26.0",
235
+ "@emdash-cms/gutenberg-to-portable-text": "0.26.0",
236
236
  "@emdash-cms/plugin-types": "0.1.0",
237
237
  "@emdash-cms/registry-client": "0.3.2"
238
238
  },
@@ -267,7 +267,7 @@
267
267
  "vite": "^6.0.0",
268
268
  "vitest": "^4.1.5",
269
269
  "zod-openapi": "^5.4.6",
270
- "@emdash-cms/blocks": "0.25.1"
270
+ "@emdash-cms/blocks": "0.26.0"
271
271
  },
272
272
  "repository": {
273
273
  "type": "git",
@@ -12,7 +12,7 @@ import { ulid } from "ulidx";
12
12
 
13
13
  import { TaxonomyRepository } from "../../database/repositories/taxonomy.js";
14
14
  import type { Database, TaxonomyDefTable } from "../../database/types.js";
15
- import { invalidateTermCache } from "../../taxonomies/index.js";
15
+ import { invalidateTaxonomyDefsCache, invalidateTermCache } from "../../taxonomies/index.js";
16
16
  import type { ApiResult } from "../types.js";
17
17
 
18
18
  const NAME_PATTERN = /^[a-z][a-z0-9_]*$/;
@@ -290,6 +290,10 @@ export async function handleTaxonomyCreate(
290
290
  })
291
291
  .execute();
292
292
 
293
+ // A new def changes which taxonomies exist — drop the isolate-wide
294
+ // defs/names caches so this isolate reflects it immediately.
295
+ invalidateTaxonomyDefsCache();
296
+
293
297
  const row = await db
294
298
  .selectFrom("_emdash_taxonomy_defs")
295
299
  .selectAll()
@@ -242,6 +242,21 @@ export function generatePluginsModule(descriptors: PluginDescriptor[]): string {
242
242
  let needsAdapter = false;
243
243
 
244
244
  descriptors.forEach((descriptor, index) => {
245
+ // Every `plugins: []` entry must resolve to a file/package entrypoint that
246
+ // can be statically imported and bundled at build time. An in-process
247
+ // `definePlugin({...})` result passed directly has no entrypoint; without
248
+ // this guard the generator emitted `import pluginDefN from "undefined";`,
249
+ // which failed deep in Rollup with `failed to resolve import "undefined"`
250
+ // (#1416). Fail fast with an actionable message instead.
251
+ if (!descriptor.entrypoint) {
252
+ throw new Error(
253
+ `[emdash] Plugin "${descriptor.id}" has no \`entrypoint\`. The astro integration's ` +
254
+ `\`plugins: []\` requires plugins that resolve to a file/package entrypoint so they can be ` +
255
+ `bundled at build time; an in-process \`definePlugin({...})\` result passed directly is not ` +
256
+ `supported. Move the plugin into its own module and reference it via a factory that returns ` +
257
+ `a descriptor with an \`entrypoint\` (e.g. \`plugins: [myPlugin()]\`).`,
258
+ );
259
+ }
245
260
  if (descriptor.format === "standard") {
246
261
  // Standard format: import default export, wrap with adaptSandboxEntry
247
262
  needsAdapter = true;
@@ -138,11 +138,12 @@ export const GET: APIRoute = async ({ params, locals, url }) => {
138
138
  lines.push(" </image:image>");
139
139
  }
140
140
 
141
- if (useXhtml && siblings && siblings.length > 1) {
141
+ const alternateEntries = siblings ?? (useXhtml ? [entry] : null);
142
+ if (useXhtml && alternateEntries) {
142
143
  // Emit one xhtml:link per sibling (including self -- Google
143
144
  // recommends including the page's own hreflang annotation).
144
145
  // Siblings with unroutable locales are skipped here too.
145
- for (const sib of siblings) {
146
+ for (const sib of alternateEntries) {
146
147
  const sibLoc = await resolveEntryUrl(sib);
147
148
  if (sibLoc === null) continue;
148
149
  lines.push(
@@ -154,13 +155,13 @@ export const GET: APIRoute = async ({ params, locals, url }) => {
154
155
  // the first sibling with a routable URL. Stable order:
155
156
  // rows arrive sorted by updated_at DESC from the handler.
156
157
  const defaultSibling =
157
- i18nConfig && siblings.find((s) => s.locale === i18nConfig.defaultLocale);
158
+ i18nConfig && alternateEntries.find((s) => s.locale === i18nConfig.defaultLocale);
158
159
  let xDefaultLoc: string | null = null;
159
160
  if (defaultSibling) {
160
161
  xDefaultLoc = await resolveEntryUrl(defaultSibling);
161
162
  }
162
163
  if (xDefaultLoc === null) {
163
- for (const sib of siblings) {
164
+ for (const sib of alternateEntries) {
164
165
  const sibLoc = await resolveEntryUrl(sib);
165
166
  if (sibLoc !== null) {
166
167
  xDefaultLoc = sibLoc;
@@ -17,8 +17,14 @@
17
17
  * ```
18
18
  */
19
19
  import type { PublicPageContext, PageMetadataContribution } from "../plugins/types.js";
20
- import { resolvePageMetadata, renderPageMetadata } from "../page/metadata.js";
20
+ import {
21
+ createSha256CspHash,
22
+ resolvePageMetadata,
23
+ renderPageMetadata,
24
+ type ResolvedPageMetadata,
25
+ } from "../page/metadata.js";
21
26
  import { renderFragments } from "../page/fragments.js";
27
+ import JsonLdScript from "./JsonLdScript.astro";
22
28
  import {
23
29
  generateBaseSeoContributions,
24
30
  generateSiteSeoContributions,
@@ -38,6 +44,7 @@ const runtime = getPageRuntime(Astro.locals as Record<string, unknown>);
38
44
  let metadataHtml = "";
39
45
  let siteIdentityHtml = "";
40
46
  let fragmentsHtml = "";
47
+ let jsonLdScripts: ResolvedPageMetadata["jsonld"] = [];
41
48
 
42
49
  if (runtime) {
43
50
  // Run independent async loads in parallel: site settings (SEO meta
@@ -79,17 +86,30 @@ if (runtime) {
79
86
  const siteContributions = generateSiteSeoContributions(siteSettings.seo);
80
87
  const allContributions = [...pluginContributions, ...siteContributions, ...baseContributions];
81
88
  const resolved = resolvePageMetadata(allContributions);
82
- metadataHtml = renderPageMetadata(resolved);
89
+ jsonLdScripts = resolved.jsonld;
90
+ metadataHtml = renderPageMetadata(resolved, { includeJsonLd: false });
83
91
  siteIdentityHtml = renderSiteIdentity({ favicon: siteSettings.favicon });
84
92
  fragmentsHtml = renderFragments(fragments, "head");
85
93
  } else {
86
94
  // No runtime (EmDash not initialized) — still render base SEO
87
95
  const baseContributions: PageMetadataContribution[] = generateBaseSeoContributions(page);
88
96
  const resolved = resolvePageMetadata(baseContributions);
89
- metadataHtml = renderPageMetadata(resolved);
97
+ jsonLdScripts = resolved.jsonld;
98
+ metadataHtml = renderPageMetadata(resolved, { includeJsonLd: false });
99
+ }
100
+
101
+ if (Astro.csp && jsonLdScripts.length > 0) {
102
+ await Promise.all(
103
+ jsonLdScripts.map(async ({ json }) => {
104
+ Astro.csp?.insertScriptHash(await createSha256CspHash(json));
105
+ }),
106
+ );
90
107
  }
91
108
  ---
92
109
 
93
110
  <Fragment set:html={metadataHtml} />
111
+ {
112
+ jsonLdScripts.map(({ json }) => <JsonLdScript json={json} />)
113
+ }
94
114
  <Fragment set:html={siteIdentityHtml} />
95
115
  <Fragment set:html={fragmentsHtml} />
@@ -0,0 +1,9 @@
1
+ ---
2
+ interface Props {
3
+ json: string;
4
+ }
5
+
6
+ const { json } = Astro.props;
7
+ ---
8
+
9
+ <script type="application/ld+json" set:html={json}></script>
@@ -199,6 +199,15 @@ async function rebuildTaxonomies(db: Kysely<unknown>, defaultLocale: string): Pr
199
199
  .on("taxonomies")
200
200
  .column("translation_group")
201
201
  .execute();
202
+ // Dropping the old table dropped idx_taxonomies_parent (from 015); recreate
203
+ // it here for parity with rebuildMenuItems/rebuildContentTaxonomies. Existing
204
+ // installs recover it via migration 047.
205
+ await db.schema
206
+ .createIndex("idx_taxonomies_parent")
207
+ .ifNotExists()
208
+ .on("taxonomies")
209
+ .column("parent_id")
210
+ .execute();
202
211
  }
203
212
 
204
213
  async function rebuildTaxonomyDefs(db: Kysely<unknown>, defaultLocale: string): Promise<void> {
@@ -569,6 +578,13 @@ async function rebuildTaxonomiesDown(db: Kysely<unknown>): Promise<void> {
569
578
  await db.schema.dropTable("taxonomies").execute();
570
579
  await sql`ALTER TABLE taxonomies_old RENAME TO taxonomies`.execute(db);
571
580
  await db.schema.createIndex("idx_taxonomies_name").on("taxonomies").column("name").execute();
581
+ // Restore the pre-036 (migration 015) parent index so the rollback is faithful.
582
+ await db.schema
583
+ .createIndex("idx_taxonomies_parent")
584
+ .ifNotExists()
585
+ .on("taxonomies")
586
+ .column("parent_id")
587
+ .execute();
572
588
  }
573
589
 
574
590
  async function rebuildTaxonomyDefsDown(db: Kysely<unknown>): Promise<void> {