emdash 0.22.0 → 0.24.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 (538) hide show
  1. package/dist/{adapters-hUy8vOB-.d.mts → adapters-DAAjOID4.d.mts} +1 -1
  2. package/dist/{adapters-hUy8vOB-.d.mts.map → adapters-DAAjOID4.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-DjlhyfNN.mjs → allowed-origins-D5FxMUo8.mjs} +2 -2
  4. package/dist/{allowed-origins-DjlhyfNN.mjs.map → allowed-origins-D5FxMUo8.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +15 -15
  7. package/dist/api/schemas/index.d.mts +1 -1
  8. package/dist/api/schemas/index.mjs +3 -3
  9. package/dist/{api-BDuM4bEy.mjs → api-d_0wi9fW.mjs} +20 -20
  10. package/dist/{api-BDuM4bEy.mjs.map → api-d_0wi9fW.mjs.map} +1 -1
  11. package/dist/{api-tokens-ViYKeIp9.mjs → api-tokens-D6ppjIVi.mjs} +2 -2
  12. package/dist/{api-tokens-ViYKeIp9.mjs.map → api-tokens-D6ppjIVi.mjs.map} +1 -1
  13. package/dist/{apply-wDqQoxX4.mjs → apply-BE9UaWX0.mjs} +17 -17
  14. package/dist/{apply-wDqQoxX4.mjs.map → apply-BE9UaWX0.mjs.map} +1 -1
  15. package/dist/astro/index.d.mts +11 -11
  16. package/dist/astro/index.mjs +36 -11
  17. package/dist/astro/index.mjs.map +1 -1
  18. package/dist/astro/middleware/auth.d.mts +9 -9
  19. package/dist/astro/middleware/auth.mjs +7 -7
  20. package/dist/astro/middleware/redirect.mjs +5 -5
  21. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  22. package/dist/astro/middleware/request-context.mjs +5 -3
  23. package/dist/astro/middleware/request-context.mjs.map +1 -1
  24. package/dist/astro/middleware/setup.mjs +1 -1
  25. package/dist/astro/middleware.d.mts +1 -1
  26. package/dist/astro/middleware.d.mts.map +1 -1
  27. package/dist/astro/middleware.mjs +362 -141
  28. package/dist/astro/middleware.mjs.map +1 -1
  29. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  30. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  31. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  32. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  33. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  34. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
  35. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
  36. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
  37. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +13 -12
  38. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  39. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +13 -12
  40. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  41. package/dist/astro/routes/api/admin/bylines/index.mjs +13 -12
  42. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  43. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
  44. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  45. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  46. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  47. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  48. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  49. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  50. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  51. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  52. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +35 -34
  53. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  54. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +35 -34
  55. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  56. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +34 -33
  57. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  58. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +34 -33
  59. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  60. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +34 -33
  61. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  62. package/dist/astro/routes/api/admin/plugins/index.mjs +34 -33
  63. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  64. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  65. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +34 -33
  66. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  67. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +34 -33
  68. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +34 -33
  70. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +34 -33
  72. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +35 -34
  74. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  75. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +34 -33
  76. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +35 -34
  78. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/plugins/updates.mjs +34 -33
  80. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  81. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +34 -33
  82. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  84. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +34 -33
  85. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  87. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  88. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  89. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  90. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  91. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  92. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  93. package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
  94. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  95. package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
  96. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  97. package/dist/astro/routes/api/auth/magic-link/send.mjs +9 -9
  98. package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
  99. package/dist/astro/routes/api/auth/me.mjs +7 -7
  100. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  101. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  102. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  103. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  104. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  105. package/dist/astro/routes/api/auth/passkey/options.mjs +11 -11
  106. package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
  107. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
  108. package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
  109. package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
  110. package/dist/astro/routes/api/auth/signup/request.mjs +9 -9
  111. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  112. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +12 -12
  113. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +11 -11
  114. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  115. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  116. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  117. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  118. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -10
  119. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +7 -7
  120. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  121. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  122. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +7 -7
  123. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +12 -12
  124. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  125. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  126. package/dist/astro/routes/api/content/_collection_/_id_.mjs +7 -7
  127. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  128. package/dist/astro/routes/api/content/_collection_/index.mjs +7 -7
  129. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  130. package/dist/astro/routes/api/dashboard.mjs +7 -7
  131. package/dist/astro/routes/api/dev/emails.mjs +3 -3
  132. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  133. package/dist/astro/routes/api/import/probe.mjs +11 -11
  134. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  135. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  136. package/dist/astro/routes/api/import/wordpress/execute.mjs +10 -9
  137. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  138. package/dist/astro/routes/api/import/wordpress/media.mjs +9 -9
  139. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  140. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +9 -9
  141. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  142. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +11 -11
  143. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  144. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +13 -12
  145. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  146. package/dist/astro/routes/api/manifest.mjs +4 -4
  147. package/dist/astro/routes/api/mcp.mjs +39 -39
  148. package/dist/astro/routes/api/media/_id_/confirm.mjs +7 -7
  149. package/dist/astro/routes/api/media/_id_.mjs +7 -7
  150. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  151. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  152. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  153. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  154. package/dist/astro/routes/api/media/upload-url.mjs +9 -9
  155. package/dist/astro/routes/api/media.mjs +12 -12
  156. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  157. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  158. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  159. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  160. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  161. package/dist/astro/routes/api/menus/index.mjs +8 -8
  162. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  163. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  164. package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
  165. package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
  166. package/dist/astro/routes/api/oauth/register.mjs +3 -3
  167. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  168. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  169. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  170. package/dist/astro/routes/api/openapi.json.mjs +4 -4
  171. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  172. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  173. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  174. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  175. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  176. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  177. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  178. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +34 -33
  179. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  180. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +34 -33
  181. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  182. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +34 -33
  183. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  184. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +34 -33
  185. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  186. package/dist/astro/routes/api/schema/collections/index.mjs +34 -33
  187. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  188. package/dist/astro/routes/api/schema/index.mjs +8 -7
  189. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  190. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +34 -33
  191. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  192. package/dist/astro/routes/api/schema/orphans/index.mjs +34 -33
  193. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  194. package/dist/astro/routes/api/search/enable.mjs +10 -10
  195. package/dist/astro/routes/api/search/index.mjs +9 -9
  196. package/dist/astro/routes/api/search/rebuild.mjs +10 -10
  197. package/dist/astro/routes/api/search/stats.mjs +6 -6
  198. package/dist/astro/routes/api/search/suggest.mjs +9 -9
  199. package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
  200. package/dist/astro/routes/api/sections/index.mjs +9 -9
  201. package/dist/astro/routes/api/settings/email.mjs +4 -4
  202. package/dist/astro/routes/api/settings.mjs +11 -11
  203. package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
  204. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  205. package/dist/astro/routes/api/setup/dev-bypass.mjs +23 -22
  206. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  207. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  208. package/dist/astro/routes/api/setup/index.mjs +24 -23
  209. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  210. package/dist/astro/routes/api/setup/status.mjs +4 -4
  211. package/dist/astro/routes/api/snapshot.mjs +6 -6
  212. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +12 -12
  213. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +12 -12
  214. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +12 -12
  215. package/dist/astro/routes/api/taxonomies/index.mjs +12 -12
  216. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  217. package/dist/astro/routes/api/typegen.mjs +5 -5
  218. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  219. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  220. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  221. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  222. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -9
  223. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -9
  224. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  225. package/dist/astro/routes/api/widget-areas/index.mjs +9 -9
  226. package/dist/astro/routes/api/widget-components.mjs +3 -3
  227. package/dist/astro/routes/robots.txt.mjs +5 -5
  228. package/dist/astro/routes/sitemap-_collection_.xml.mjs +8 -8
  229. package/dist/astro/routes/sitemap.xml.mjs +6 -6
  230. package/dist/astro/types.d.mts +12 -12
  231. package/dist/auth/providers/github.d.mts +1 -1
  232. package/dist/auth/providers/google.d.mts +1 -1
  233. package/dist/{authorize-C0EYoUeV.mjs → authorize-B__u-iZJ.mjs} +2 -2
  234. package/dist/{authorize-C0EYoUeV.mjs.map → authorize-B__u-iZJ.mjs.map} +1 -1
  235. package/dist/{byline-CC0WS4Gu.mjs → byline-C-YajfV7.mjs} +18 -76
  236. package/dist/byline-C-YajfV7.mjs.map +1 -0
  237. package/dist/{byline-fields-53i9Et0x.mjs → byline-fields-B-KXkg5R.mjs} +2 -2
  238. package/dist/{byline-fields-53i9Et0x.mjs.map → byline-fields-B-KXkg5R.mjs.map} +1 -1
  239. package/dist/{byline-fields-DNp0HNKc.mjs → byline-fields-DaMKzkhO.mjs} +1 -1
  240. package/dist/{byline-fields-DNp0HNKc.mjs.map → byline-fields-DaMKzkhO.mjs.map} +1 -1
  241. package/dist/{byline-fields-jy07PZJM.d.mts → byline-fields-RFPkDLRw.d.mts} +21 -1
  242. package/dist/byline-fields-RFPkDLRw.d.mts.map +1 -0
  243. package/dist/{bylines-CCO1CYnH.mjs → bylines-BMtJnPnG.mjs} +7 -7
  244. package/dist/{bylines-CCO1CYnH.mjs.map → bylines-BMtJnPnG.mjs.map} +1 -1
  245. package/dist/{bylines-DGekMGGm.mjs → bylines-D4KADDAi.mjs} +4 -4
  246. package/dist/{bylines-DGekMGGm.mjs.map → bylines-D4KADDAi.mjs.map} +1 -1
  247. package/dist/{cache-pXTpun6s.mjs → cache-BUJe2w6Z.mjs} +2 -2
  248. package/dist/{cache-pXTpun6s.mjs.map → cache-BUJe2w6Z.mjs.map} +1 -1
  249. package/dist/{challenge-store-C6E_kWTs.mjs → challenge-store-LhiqMccz.mjs} +1 -1
  250. package/dist/{challenge-store-C6E_kWTs.mjs.map → challenge-store-LhiqMccz.mjs.map} +1 -1
  251. package/dist/{chunks-SyjZaYwV.mjs → chunks-BHgcHh2C.mjs} +2 -2
  252. package/dist/{chunks-SyjZaYwV.mjs.map → chunks-BHgcHh2C.mjs.map} +1 -1
  253. package/dist/cli/index.mjs +25 -24
  254. package/dist/cli/index.mjs.map +1 -1
  255. package/dist/client/cf-access.d.mts +1 -1
  256. package/dist/client/index.d.mts +1 -1
  257. package/dist/client/index.mjs +2 -2
  258. package/dist/{comment-80UyJJUR.mjs → comment-BDx50EX5.mjs} +2 -2
  259. package/dist/{comment-80UyJJUR.mjs.map → comment-BDx50EX5.mjs.map} +1 -1
  260. package/dist/{comment-reaction-B6AdcpXw.mjs → comment-reaction-Gv_pgfI3.mjs} +2 -2
  261. package/dist/{comment-reaction-B6AdcpXw.mjs.map → comment-reaction-Gv_pgfI3.mjs.map} +1 -1
  262. package/dist/{comments-CtPkT2tp.mjs → comments-CXHuQ6jI.mjs} +3 -3
  263. package/dist/{comments-CtPkT2tp.mjs.map → comments-CXHuQ6jI.mjs.map} +1 -1
  264. package/dist/{components-D6ZnrzbB.mjs → components-CYt4uVK9.mjs} +1 -1
  265. package/dist/{components-D6ZnrzbB.mjs.map → components-CYt4uVK9.mjs.map} +1 -1
  266. package/dist/{content-2PTDNnoh.mjs → content-PoVGKjp0.mjs} +3 -3
  267. package/dist/{content-2PTDNnoh.mjs.map → content-PoVGKjp0.mjs.map} +1 -1
  268. package/dist/{context-f1__jpzF.mjs → context-Dvws41_A.mjs} +62 -24
  269. package/dist/context-Dvws41_A.mjs.map +1 -0
  270. package/dist/{cron-CQPxBjYs.mjs → cron-C5LVoNmP.mjs} +14 -2
  271. package/dist/cron-C5LVoNmP.mjs.map +1 -0
  272. package/dist/{dashboard-BOESNcwE.mjs → dashboard-DsYU8JKS.mjs} +4 -4
  273. package/dist/{dashboard-BOESNcwE.mjs.map → dashboard-DsYU8JKS.mjs.map} +1 -1
  274. package/dist/db/index.d.mts +3 -3
  275. package/dist/db/index.mjs +1 -1
  276. package/dist/db/libsql.d.mts +1 -1
  277. package/dist/db/postgres.d.mts +1 -1
  278. package/dist/db/sqlite.d.mts +1 -1
  279. package/dist/{db-errors-CrC5w3Jz.mjs → db-errors-UXB1CK42.mjs} +1 -1
  280. package/dist/{db-errors-CrC5w3Jz.mjs.map → db-errors-UXB1CK42.mjs.map} +1 -1
  281. package/dist/{default-CpBEPn6a.mjs → default-CeRG-Ot4.mjs} +1 -1
  282. package/dist/{default-CpBEPn6a.mjs.map → default-CeRG-Ot4.mjs.map} +1 -1
  283. package/dist/{device-flow-Bn-qiDrw.mjs → device-flow-DENDCQ9F.mjs} +4 -4
  284. package/dist/{device-flow-Bn-qiDrw.mjs.map → device-flow-DENDCQ9F.mjs.map} +1 -1
  285. package/dist/{email-console-B9VS6eN4.mjs → email-console-DJP32ucW.mjs} +1 -1
  286. package/dist/{email-console-B9VS6eN4.mjs.map → email-console-DJP32ucW.mjs.map} +1 -1
  287. package/dist/{error-BQ1pxs0L.mjs → error-lq_fL-RS.mjs} +2 -2
  288. package/dist/{error-BQ1pxs0L.mjs.map → error-lq_fL-RS.mjs.map} +1 -1
  289. package/dist/{escape-BtbJWyaW.mjs → escape-Bjio4ZsM.mjs} +1 -1
  290. package/dist/{escape-BtbJWyaW.mjs.map → escape-Bjio4ZsM.mjs.map} +1 -1
  291. package/dist/field-defs-cache-CYYx8xlG.mjs +77 -0
  292. package/dist/field-defs-cache-CYYx8xlG.mjs.map +1 -0
  293. package/dist/{fts-manager-DtiWqwNJ.mjs → fts-manager-DW_NR-Fs.mjs} +2 -2
  294. package/dist/{fts-manager-DtiWqwNJ.mjs.map → fts-manager-DW_NR-Fs.mjs.map} +1 -1
  295. package/dist/{hash-BowcuPwv.mjs → hash-DrvzIXcz.mjs} +1 -1
  296. package/dist/{hash-BowcuPwv.mjs.map → hash-DrvzIXcz.mjs.map} +1 -1
  297. package/dist/{import-DqSGOlmR.mjs → import-Bb1T9WJS.mjs} +3 -3
  298. package/dist/{import-DqSGOlmR.mjs.map → import-Bb1T9WJS.mjs.map} +1 -1
  299. package/dist/{index-C2dLNT6S.d.mts → index-BroXT-vD.d.mts} +136 -38
  300. package/dist/index-BroXT-vD.d.mts.map +1 -0
  301. package/dist/{index-ZDEwR7qW.d.mts → index-qbL02uhs.d.mts} +3 -3
  302. package/dist/{index-ZDEwR7qW.d.mts.map → index-qbL02uhs.d.mts.map} +1 -1
  303. package/dist/index.d.mts +17 -17
  304. package/dist/index.mjs +55 -54
  305. package/dist/{load-Dtoo7KcJ.mjs → load-DMFi4n1p.mjs} +2 -2
  306. package/dist/{load-Dtoo7KcJ.mjs.map → load-DMFi4n1p.mjs.map} +1 -1
  307. package/dist/{loader-C8Z48Uof.mjs → loader-BR2SgfEc.mjs} +77 -12
  308. package/dist/loader-BR2SgfEc.mjs.map +1 -0
  309. package/dist/{manifest-schema-DDSbwkAL.mjs → manifest-schema-BWKnQYQF.mjs} +1 -1
  310. package/dist/{manifest-schema-DDSbwkAL.mjs.map → manifest-schema-BWKnQYQF.mjs.map} +1 -1
  311. package/dist/media/image-endpoint.mjs +1 -1
  312. package/dist/media/index.d.mts +1 -1
  313. package/dist/media/index.mjs +2 -2
  314. package/dist/media/local-runtime.d.mts +19 -11
  315. package/dist/media/local-runtime.d.mts.map +1 -1
  316. package/dist/media/local-runtime.mjs +11 -9
  317. package/dist/media/local-runtime.mjs.map +1 -1
  318. package/dist/{media-Bw0sA6rb.mjs → media-CTjJoJTh.mjs} +2 -2
  319. package/dist/{media-Bw0sA6rb.mjs.map → media-CTjJoJTh.mjs.map} +1 -1
  320. package/dist/{media-allowlist-DunzrKFM.mjs → media-allowlist-B77I5_bw.mjs} +2 -2
  321. package/dist/{media-allowlist-DunzrKFM.mjs.map → media-allowlist-B77I5_bw.mjs.map} +1 -1
  322. package/dist/{media-url-S22B6aPr.mjs → media-url-O4rm9-SQ.mjs} +1 -1
  323. package/dist/{media-url-S22B6aPr.mjs.map → media-url-O4rm9-SQ.mjs.map} +1 -1
  324. package/dist/{menus-CYs0WZoL.mjs → menus-8O6xNcYW.mjs} +2 -2
  325. package/dist/{menus-CYs0WZoL.mjs.map → menus-8O6xNcYW.mjs.map} +1 -1
  326. package/dist/{menus-DOs1MQJg.mjs → menus-GvRSpgIA.mjs} +57 -14
  327. package/dist/menus-GvRSpgIA.mjs.map +1 -0
  328. package/dist/{mime-DYpsOAny.mjs → mime-BTzvzJ8M.mjs} +1 -1
  329. package/dist/{mime-DYpsOAny.mjs.map → mime-BTzvzJ8M.mjs.map} +1 -1
  330. package/dist/{mode-CFh8Dw8G.mjs → mode-BB0F8xTC.mjs} +1 -1
  331. package/dist/{mode-CFh8Dw8G.mjs.map → mode-BB0F8xTC.mjs.map} +1 -1
  332. package/dist/{normalize-Uam4_Vkr.mjs → normalize-ClbNIenm.mjs} +1 -1
  333. package/dist/{normalize-Uam4_Vkr.mjs.map → normalize-ClbNIenm.mjs.map} +1 -1
  334. package/dist/{oauth-authorization-BN-t83Uy.mjs → oauth-authorization-CsvzIp_F.mjs} +4 -4
  335. package/dist/{oauth-authorization-BN-t83Uy.mjs.map → oauth-authorization-CsvzIp_F.mjs.map} +1 -1
  336. package/dist/{oauth-clients-R6I_V0qz.mjs → oauth-clients-C9SYwEbZ.mjs} +1 -1
  337. package/dist/{oauth-clients-R6I_V0qz.mjs.map → oauth-clients-C9SYwEbZ.mjs.map} +1 -1
  338. package/dist/{oauth-state-store-Csoc6kkR.mjs → oauth-state-store---zrApfB.mjs} +1 -1
  339. package/dist/{oauth-state-store-Csoc6kkR.mjs.map → oauth-state-store---zrApfB.mjs.map} +1 -1
  340. package/dist/{oauth-user-lookup-d7VYjTpx.mjs → oauth-user-lookup-SHsWRlG9.mjs} +1 -1
  341. package/dist/{oauth-user-lookup-d7VYjTpx.mjs.map → oauth-user-lookup-SHsWRlG9.mjs.map} +1 -1
  342. package/dist/object-cache/memory.d.mts +1 -1
  343. package/dist/{options-DFFpvNJU.d.mts → options-CqECG0gM.d.mts} +3 -3
  344. package/dist/{options-DFFpvNJU.d.mts.map → options-CqECG0gM.d.mts.map} +1 -1
  345. package/dist/page/index.d.mts +2 -2
  346. package/dist/{parse-qrHlnzTy.mjs → parse-DHI873qU.mjs} +2 -2
  347. package/dist/{parse-qrHlnzTy.mjs.map → parse-DHI873qU.mjs.map} +1 -1
  348. package/dist/{passkey-config-BJTbcnI5.mjs → passkey-config-C0YfSBko.mjs} +1 -1
  349. package/dist/{passkey-config-BJTbcnI5.mjs.map → passkey-config-C0YfSBko.mjs.map} +1 -1
  350. package/dist/{placeholder-CIDCpyEY.mjs → placeholder-B0s2mWu5.mjs} +1 -1
  351. package/dist/{placeholder-CIDCpyEY.mjs.map → placeholder-B0s2mWu5.mjs.map} +1 -1
  352. package/dist/{placeholder-CirPauNG.d.mts → placeholder-QoI4gEzP.d.mts} +1 -1
  353. package/dist/{placeholder-CirPauNG.d.mts.map → placeholder-QoI4gEzP.d.mts.map} +1 -1
  354. package/dist/plugin-types.d.mts +1 -1
  355. package/dist/plugin-utils.d.mts +9 -9
  356. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  357. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  358. package/dist/{portable-text-BIFhrU6T.mjs → portable-text-BICg8bTk.mjs} +1 -1
  359. package/dist/{portable-text-BIFhrU6T.mjs.map → portable-text-BICg8bTk.mjs.map} +1 -1
  360. package/dist/{preview-IDye9SPQ.mjs → preview-DKGCt2_p.mjs} +2 -2
  361. package/dist/{preview-IDye9SPQ.mjs.map → preview-DKGCt2_p.mjs.map} +1 -1
  362. package/dist/{public-url-8lFyQ8ZF.mjs → public-url-CTVqgMmg.mjs} +1 -1
  363. package/dist/{public-url-8lFyQ8ZF.mjs.map → public-url-CTVqgMmg.mjs.map} +1 -1
  364. package/dist/{query-B1RVFUNM.mjs → query-DZdlS6Cu.mjs} +92 -14
  365. package/dist/query-DZdlS6Cu.mjs.map +1 -0
  366. package/dist/{rate-limit-DfNAqIWW.mjs → rate-limit-D1Y6VFVP.mjs} +2 -2
  367. package/dist/{rate-limit-DfNAqIWW.mjs.map → rate-limit-D1Y6VFVP.mjs.map} +1 -1
  368. package/dist/{redirect-Bifv_yHv.mjs → redirect-C1KVGYWe.mjs} +2 -2
  369. package/dist/{redirect-Bifv_yHv.mjs.map → redirect-C1KVGYWe.mjs.map} +1 -1
  370. package/dist/{redirect-B4wuX2A5.mjs → redirect-CS-PHtNh.mjs} +1 -1
  371. package/dist/{redirect-B4wuX2A5.mjs.map → redirect-CS-PHtNh.mjs.map} +1 -1
  372. package/dist/{redirects-CT_vb3NV.mjs → redirects-DgnypVir.mjs} +4 -4
  373. package/dist/{redirects-CT_vb3NV.mjs.map → redirects-DgnypVir.mjs.map} +1 -1
  374. package/dist/{redirects-DUvXgzCX.mjs → redirects-x9eFPeyi.mjs} +4 -2
  375. package/dist/redirects-x9eFPeyi.mjs.map +1 -0
  376. package/dist/{registry-CjK96fHD.mjs → registry-CJkCubOJ.mjs} +4 -4
  377. package/dist/{registry-CjK96fHD.mjs.map → registry-CJkCubOJ.mjs.map} +1 -1
  378. package/dist/{request-meta-ei3ATilC.mjs → request-meta-CmS1tDFf.mjs} +2 -2
  379. package/dist/{request-meta-ei3ATilC.mjs.map → request-meta-CmS1tDFf.mjs.map} +1 -1
  380. package/dist/{resolve-CpbBglV0.mjs → resolve-C7I0qiR0.mjs} +1 -1
  381. package/dist/{resolve-CpbBglV0.mjs.map → resolve-C7I0qiR0.mjs.map} +1 -1
  382. package/dist/{runner-DYYnW530.mjs → runner-BsquExlK.mjs} +246 -174
  383. package/dist/runner-BsquExlK.mjs.map +1 -0
  384. package/dist/{runner-jdXtFEd3.d.mts → runner-CEtq-6Sm.d.mts} +2 -2
  385. package/dist/{runner-jdXtFEd3.d.mts.map → runner-CEtq-6Sm.d.mts.map} +1 -1
  386. package/dist/runtime.d.mts +10 -10
  387. package/dist/runtime.mjs +2 -2
  388. package/dist/{schema-5z2Jfnbm.mjs → schema-DMjXuvNU.mjs} +4 -4
  389. package/dist/{schema-5z2Jfnbm.mjs.map → schema-DMjXuvNU.mjs.map} +1 -1
  390. package/dist/{search-D9FkoM-k.mjs → search-D1wKk3lc.mjs} +4 -4
  391. package/dist/{search-D9FkoM-k.mjs.map → search-D1wKk3lc.mjs.map} +1 -1
  392. package/dist/{secrets-BUE5UQys.mjs → secrets-BSf9pRRY.mjs} +2 -2
  393. package/dist/{secrets-BUE5UQys.mjs.map → secrets-BSf9pRRY.mjs.map} +1 -1
  394. package/dist/{sections-DWYZo6HK.mjs → sections-CCMic8dB.mjs} +3 -3
  395. package/dist/{sections-DWYZo6HK.mjs.map → sections-CCMic8dB.mjs.map} +1 -1
  396. package/dist/seed/index.d.mts +2 -2
  397. package/dist/seed/index.mjs +17 -16
  398. package/dist/seo/index.d.mts +1 -1
  399. package/dist/seo/index.mjs +1 -1
  400. package/dist/{seo-Cu47j3mJ.mjs → seo-3QrZ1EPT.mjs} +2 -2
  401. package/dist/{seo-Cu47j3mJ.mjs.map → seo-3QrZ1EPT.mjs.map} +1 -1
  402. package/dist/{seo-DRnqnstF.mjs → seo-DCEF-AVr.mjs} +1 -1
  403. package/dist/{seo-DRnqnstF.mjs.map → seo-DCEF-AVr.mjs.map} +1 -1
  404. package/dist/{service-DTmtqCnw.mjs → service-CQHK_10l.mjs} +3 -3
  405. package/dist/{service-DTmtqCnw.mjs.map → service-CQHK_10l.mjs.map} +1 -1
  406. package/dist/{session-user-DmtPMNa1.mjs → session-user-B8aLtKAH.mjs} +1 -1
  407. package/dist/{session-user-DmtPMNa1.mjs.map → session-user-B8aLtKAH.mjs.map} +1 -1
  408. package/dist/{settings-DUrVDN0c.mjs → settings-C3Yi5Co3.mjs} +3 -3
  409. package/dist/{settings-DUrVDN0c.mjs.map → settings-C3Yi5Co3.mjs.map} +1 -1
  410. package/dist/{settings-yaxu9mU-.mjs → settings-D9rvaLuY.mjs} +4 -4
  411. package/dist/{settings-yaxu9mU-.mjs.map → settings-D9rvaLuY.mjs.map} +1 -1
  412. package/dist/{setup-complete-BWFHzgvZ.mjs → setup-complete-gEiySUc-.mjs} +1 -1
  413. package/dist/{setup-complete-BWFHzgvZ.mjs.map → setup-complete-gEiySUc-.mjs.map} +1 -1
  414. package/dist/{setup-nonce-DFWG-J5c.mjs → setup-nonce-DzS50zme.mjs} +1 -1
  415. package/dist/{setup-nonce-DFWG-J5c.mjs.map → setup-nonce-DzS50zme.mjs.map} +1 -1
  416. package/dist/{single-flight-cache-B5p5cNOL.mjs → single-flight-cache-Cdfkic3t.mjs} +1 -1
  417. package/dist/{single-flight-cache-B5p5cNOL.mjs.map → single-flight-cache-Cdfkic3t.mjs.map} +1 -1
  418. package/dist/{site-url-BOTPgmeM.mjs → site-url-BLebyON8.mjs} +1 -1
  419. package/dist/{site-url-BOTPgmeM.mjs.map → site-url-BLebyON8.mjs.map} +1 -1
  420. package/dist/{ssrf-B0VeRLrp.mjs → ssrf-BRKb343l.mjs} +1 -1
  421. package/dist/{ssrf-B0VeRLrp.mjs.map → ssrf-BRKb343l.mjs.map} +1 -1
  422. package/dist/{status-CB2basWJ.mjs → status-vUK0SA17.mjs} +1 -1
  423. package/dist/{status-CB2basWJ.mjs.map → status-vUK0SA17.mjs.map} +1 -1
  424. package/dist/storage/local.d.mts +1 -1
  425. package/dist/storage/local.mjs +1 -1
  426. package/dist/storage/s3.d.mts +1 -1
  427. package/dist/storage/s3.mjs +1 -1
  428. package/dist/{taxonomies-Be9B1jEj.mjs → taxonomies-CurpLtNC.mjs} +52 -24
  429. package/dist/taxonomies-CurpLtNC.mjs.map +1 -0
  430. package/dist/{taxonomies-CJmkEpdS.mjs → taxonomies-DhLaNJji.mjs} +34 -27
  431. package/dist/taxonomies-DhLaNJji.mjs.map +1 -0
  432. package/dist/{taxonomy-B2HQuVf5.mjs → taxonomy-BBUlGug5.mjs} +33 -7
  433. package/dist/taxonomy-BBUlGug5.mjs.map +1 -0
  434. package/dist/{tokens-BvrJvIB6.mjs → tokens-LeuXF9gG.mjs} +1 -1
  435. package/dist/{tokens-BvrJvIB6.mjs.map → tokens-LeuXF9gG.mjs.map} +1 -1
  436. package/dist/{transport-k7YjfmEn.d.mts → transport-BZ-03aFT.d.mts} +1 -1
  437. package/dist/{transport-k7YjfmEn.d.mts.map → transport-BZ-03aFT.d.mts.map} +1 -1
  438. package/dist/{transport-B4rXnSTf.mjs → transport-_2nBz7e9.mjs} +1 -1
  439. package/dist/{transport-B4rXnSTf.mjs.map → transport-_2nBz7e9.mjs.map} +1 -1
  440. package/dist/{trusted-proxy-DY1_UqHr.mjs → trusted-proxy-DZY5WCn2.mjs} +1 -1
  441. package/dist/{trusted-proxy-DY1_UqHr.mjs.map → trusted-proxy-DZY5WCn2.mjs.map} +1 -1
  442. package/dist/{types-Czg1S3gB.d.mts → types-B-gIxFCE.d.mts} +1 -1
  443. package/dist/{types-Czg1S3gB.d.mts.map → types-B-gIxFCE.d.mts.map} +1 -1
  444. package/dist/{types-DXLVzV2w.d.mts → types-BG8_XTLj.d.mts} +1 -1
  445. package/dist/{types-DXLVzV2w.d.mts.map → types-BG8_XTLj.d.mts.map} +1 -1
  446. package/dist/{types-CWGYDYqF.d.mts → types-BWh2MjmA.d.mts} +15 -2
  447. package/dist/types-BWh2MjmA.d.mts.map +1 -0
  448. package/dist/{types-CgvhU-hF.d.mts → types-BWqGVhFn.d.mts} +1 -1
  449. package/dist/{types-CgvhU-hF.d.mts.map → types-BWqGVhFn.d.mts.map} +1 -1
  450. package/dist/{types-DYF2_Vf4.mjs → types-CQAugunJ.mjs} +1 -1
  451. package/dist/{types-DYF2_Vf4.mjs.map → types-CQAugunJ.mjs.map} +1 -1
  452. package/dist/{types-DPCpkzvS.d.mts → types-CTLJYMhN.d.mts} +1 -1
  453. package/dist/{types-DPCpkzvS.d.mts.map → types-CTLJYMhN.d.mts.map} +1 -1
  454. package/dist/{types-CmivgZbp.d.mts → types-CUsXGRVj.d.mts} +1 -1
  455. package/dist/{types-CmivgZbp.d.mts.map → types-CUsXGRVj.d.mts.map} +1 -1
  456. package/dist/{types-DXOIbece.mjs → types-CmI1OyQg.mjs} +2 -2
  457. package/dist/types-CmI1OyQg.mjs.map +1 -0
  458. package/dist/{types-CKNXt63n.d.mts → types-DrYMm9s5.d.mts} +1 -1
  459. package/dist/{types-CKNXt63n.d.mts.map → types-DrYMm9s5.d.mts.map} +1 -1
  460. package/dist/{types-CIKX2HO5.d.mts → types-TTx8uP7V.d.mts} +2 -2
  461. package/dist/{types-CIKX2HO5.d.mts.map → types-TTx8uP7V.d.mts.map} +1 -1
  462. package/dist/{types-DCNNfeCv.d.mts → types-fS7-VTdR.d.mts} +1 -1
  463. package/dist/{types-DCNNfeCv.d.mts.map → types-fS7-VTdR.d.mts.map} +1 -1
  464. package/dist/{user-Db-Rti_z.mjs → user-JAuc8VUf.mjs} +3 -3
  465. package/dist/{user-Db-Rti_z.mjs.map → user-JAuc8VUf.mjs.map} +1 -1
  466. package/dist/{utils-LbHcSRTj.mjs → utils-B7A57fm9.mjs} +1 -1
  467. package/dist/{utils-LbHcSRTj.mjs.map → utils-B7A57fm9.mjs.map} +1 -1
  468. package/dist/{validate-Bwl64sxv.mjs → validate-BzXHWnkV.mjs} +2 -2
  469. package/dist/{validate-Bwl64sxv.mjs.map → validate-BzXHWnkV.mjs.map} +1 -1
  470. package/dist/{validate-CE2M0nvx.d.mts → validate-DOFrIb-c.d.mts} +5 -5
  471. package/dist/{validate-CE2M0nvx.d.mts.map → validate-DOFrIb-c.d.mts.map} +1 -1
  472. package/dist/{validation-C_yOvFaO.mjs → validation-CY1gk4Ar.mjs} +6 -6
  473. package/dist/{validation-C_yOvFaO.mjs.map → validation-CY1gk4Ar.mjs.map} +1 -1
  474. package/dist/version-B5nHxI5Y.mjs +7 -0
  475. package/dist/{version-RnYXYjzD.mjs.map → version-B5nHxI5Y.mjs.map} +1 -1
  476. package/dist/{widgets-8GONuRHo.mjs → widgets-Dri0spI4.mjs} +3 -3
  477. package/dist/{widgets-8GONuRHo.mjs.map → widgets-Dri0spI4.mjs.map} +1 -1
  478. package/dist/{zod-generator-D481JjzD.mjs → zod-generator-xEHODMJf.mjs} +51 -15
  479. package/dist/zod-generator-xEHODMJf.mjs.map +1 -0
  480. package/package.json +8 -7
  481. package/src/api/handlers/taxonomies.ts +62 -32
  482. package/src/api/schemas/bylines.ts +8 -0
  483. package/src/astro/integration/shims/use-sync-external-store-with-selector.js +71 -0
  484. package/src/astro/integration/shims/use-sync-external-store.js +1 -0
  485. package/src/astro/integration/virtual-modules.ts +8 -0
  486. package/src/astro/integration/vite-config.ts +56 -9
  487. package/src/astro/middleware/request-context.ts +6 -1
  488. package/src/astro/middleware/scoped-db.ts +131 -0
  489. package/src/astro/middleware/stream-end-metrics.ts +4 -8
  490. package/src/astro/middleware.ts +103 -31
  491. package/src/astro/routes/admin.astro +5 -1
  492. package/src/astro/routes/api/schema/index.ts +9 -3
  493. package/src/cli/commands/export-seed.ts +15 -11
  494. package/src/components/Block.astro +71 -0
  495. package/src/components/EmDashMedia.astro +1 -2
  496. package/src/components/Image.astro +36 -2
  497. package/src/components/InlinePortableTextEditor.tsx +10 -2
  498. package/src/components/index.ts +5 -0
  499. package/src/components/portable-text-text-align.ts +38 -0
  500. package/src/content/converters/portable-text-to-prosemirror.ts +5 -1
  501. package/src/content/converters/prosemirror-to-portable-text.ts +8 -0
  502. package/src/content/converters/types.ts +1 -0
  503. package/src/database/migrations/045_taxonomy_parent_group.ts +68 -0
  504. package/src/database/migrations/runner.ts +2 -0
  505. package/src/database/repositories/byline.ts +14 -0
  506. package/src/database/repositories/taxonomy.ts +50 -7
  507. package/src/database/repositories/types.ts +13 -0
  508. package/src/emdash-runtime.ts +226 -71
  509. package/src/loader.ts +161 -13
  510. package/src/media/local-runtime.ts +18 -5
  511. package/src/plugins/context.ts +94 -22
  512. package/src/plugins/cron.ts +18 -2
  513. package/src/plugins/routes.ts +40 -1
  514. package/src/query.ts +201 -19
  515. package/src/schema/zod-generator.ts +53 -8
  516. package/src/taxonomies/index.ts +46 -8
  517. package/src/ui.ts +5 -0
  518. package/src/virtual-modules.d.ts +13 -0
  519. package/dist/byline-CC0WS4Gu.mjs.map +0 -1
  520. package/dist/byline-fields-jy07PZJM.d.mts.map +0 -1
  521. package/dist/context-f1__jpzF.mjs.map +0 -1
  522. package/dist/cron-CQPxBjYs.mjs.map +0 -1
  523. package/dist/index-C2dLNT6S.d.mts.map +0 -1
  524. package/dist/loader-C8Z48Uof.mjs.map +0 -1
  525. package/dist/menus-DOs1MQJg.mjs.map +0 -1
  526. package/dist/query-B1RVFUNM.mjs.map +0 -1
  527. package/dist/redirects-DUvXgzCX.mjs.map +0 -1
  528. package/dist/runner-DYYnW530.mjs.map +0 -1
  529. package/dist/taxonomies-Be9B1jEj.mjs.map +0 -1
  530. package/dist/taxonomies-CJmkEpdS.mjs.map +0 -1
  531. package/dist/taxonomy-B2HQuVf5.mjs.map +0 -1
  532. package/dist/types-CWGYDYqF.d.mts.map +0 -1
  533. package/dist/types-DXOIbece.mjs.map +0 -1
  534. package/dist/version-RnYXYjzD.mjs +0 -7
  535. package/dist/zod-generator-D481JjzD.mjs.map +0 -1
  536. /package/dist/{api-tokens-BVH-H8Z9.mjs → api-tokens-B4BQybOp.mjs} +0 -0
  537. /package/dist/{ssrf-Cz1e2QPn.mjs → ssrf-CcX9zvMK.mjs} +0 -0
  538. /package/dist/{types-Bfr2Pj6z.mjs → types-BvbeGEtr.mjs} +0 -0
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Request-scoped database lifecycle helpers.
3
+ *
4
+ * Extracted from middleware.ts so they can be unit-tested without pulling in
5
+ * the virtual:emdash/* module graph. The middleware imports these to settle a
6
+ * request-scoped db adapter's lifecycle around the response.
7
+ */
8
+
9
+ /**
10
+ * Astro attaches AstroCookies to outgoing responses via a well-known global
11
+ * symbol. Cloning a Response (`new Response(body, init)`) drops non-header
12
+ * metadata, so any helper that wraps the response must explicitly forward this
13
+ * symbol or `cookies.set()` calls will be silently dropped. `Symbol.for`
14
+ * returns the same registry symbol everywhere, so this matches the copy in
15
+ * middleware.ts.
16
+ */
17
+ export const ASTRO_COOKIES_SYMBOL = Symbol.for("astro.cookies");
18
+
19
+ /**
20
+ * Run a request-scoped db's `close()` once the response body has finished
21
+ * streaming. Astro streams HTML and components issue DB queries during that
22
+ * stream, so a connection-backed adapter (e.g. Postgres over Hyperdrive) must
23
+ * not be torn down until the body is flushed. Bodyless responses (redirects,
24
+ * 304s, errors) close immediately. A guard makes close idempotent and a stream
25
+ * `cancel` (client disconnect) still triggers it so connections never leak.
26
+ *
27
+ * No-op for adapters without a `close` (D1): the response passes through.
28
+ */
29
+ export function wrapResponseForScopedClose(response: Response, close: () => void): Response {
30
+ let closed = false;
31
+ const runClose = () => {
32
+ if (closed) return;
33
+ closed = true;
34
+ try {
35
+ close();
36
+ } catch (error) {
37
+ console.error("[emdash] request-scoped db close failed:", error);
38
+ }
39
+ };
40
+
41
+ if (!response.body) {
42
+ runClose();
43
+ return response;
44
+ }
45
+
46
+ const transform = new TransformStream<Uint8Array, Uint8Array>({
47
+ flush: runClose,
48
+ cancel: runClose,
49
+ });
50
+ const wrapped = new Response(response.body.pipeThrough(transform), response);
51
+ const astroCookies = Reflect.get(response, ASTRO_COOKIES_SYMBOL);
52
+ if (astroCookies !== undefined) {
53
+ Reflect.set(wrapped, ASTRO_COOKIES_SYMBOL, astroCookies);
54
+ }
55
+ // Byte counts are preserved by the identity transform, but a stale
56
+ // Content-Length on a reconstructed streaming Response risks truncation.
57
+ wrapped.headers.delete("Content-Length");
58
+ return wrapped;
59
+ }
60
+
61
+ /**
62
+ * Run the request body under a request-scoped db, then settle its lifecycle:
63
+ * `commit()` runs before the response is returned (so per-request state like a
64
+ * D1 bookmark cookie is persisted in the headers, even if render throws), while
65
+ * `close()` (if any) is deferred to stream-end so a connection-backed adapter
66
+ * isn't torn down mid-render. On error the connection is closed immediately
67
+ * before rethrowing so it never leaks.
68
+ *
69
+ * On the error path both `commit()` and `close()` are defended: a throw from
70
+ * either is logged and swallowed so it can't replace the propagating render
71
+ * error (which is the one the caller needs to see). On the success path
72
+ * `commit()` is guarded too — if it throws, the connection is closed before the
73
+ * failure is surfaced, so it never leaks. For the current adapters `commit()`
74
+ * is a no-op (Hyperdrive) or a cookie write (D1, no `close`) and `close()` is
75
+ * fire-and-forget, so these guards only matter for a future connection-backed
76
+ * adapter with throwing teardown, but the helper is generic and must not leak
77
+ * or mask.
78
+ */
79
+ export async function finishScoped(
80
+ scoped: { commit: () => void; close?: () => void },
81
+ run: () => Promise<Response>,
82
+ ): Promise<Response> {
83
+ let response: Response;
84
+ try {
85
+ response = await run();
86
+ } catch (error) {
87
+ // A render error is already propagating; neither commit nor close may
88
+ // mask it, and close must still run so the connection doesn't leak.
89
+ commitSafely(scoped.commit);
90
+ closeSafely(scoped.close);
91
+ throw error;
92
+ }
93
+ try {
94
+ scoped.commit();
95
+ } catch (error) {
96
+ // commit() failed on the success path: close the connection now (the
97
+ // response won't be wrapped, so stream-end close would never run) and
98
+ // surface the failure. close is swallowed so it can't mask the commit
99
+ // error that the caller needs to see.
100
+ closeSafely(scoped.close);
101
+ throw error;
102
+ }
103
+ return scoped.close ? wrapResponseForScopedClose(response, scoped.close) : response;
104
+ }
105
+
106
+ /**
107
+ * Run commit() swallowing any error. Used where an exception is already
108
+ * propagating (or about to be thrown) and a commit failure must neither mask it
109
+ * nor skip the subsequent close().
110
+ */
111
+ function commitSafely(commit: () => void): void {
112
+ try {
113
+ commit();
114
+ } catch (error) {
115
+ console.error("[emdash] request-scoped db commit failed during error handling:", error);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Run close() swallowing any error. Used on the error/commit-failure paths
121
+ * where another exception is the one the caller must see; a throwing teardown
122
+ * must not replace it.
123
+ */
124
+ function closeSafely(close: (() => void) | undefined): void {
125
+ if (!close) return;
126
+ try {
127
+ close();
128
+ } catch (error) {
129
+ console.error("[emdash] request-scoped db close failed during error handling:", error);
130
+ }
131
+ }
@@ -20,17 +20,13 @@
20
20
 
21
21
  import { flushRecorder, isInstrumentationEnabled } from "../../database/instrumentation.js";
22
22
  import { getRequestContext } from "../../request-context.js";
23
+ // Reuse the single source of truth for Astro's well-known cookies symbol
24
+ // rather than redefining `Symbol.for("astro.cookies")` here — it must stay in
25
+ // lockstep with the copy the rest of the middleware forwards.
26
+ import { ASTRO_COOKIES_SYMBOL } from "./scoped-db.js";
23
27
 
24
28
  export const STREAM_END_PREFIX = "[emdash-stream-end]";
25
29
 
26
- /**
27
- * Astro attaches AstroCookies to outgoing responses via a well-known global
28
- * symbol. Constructing a new Response drops non-header metadata, so the
29
- * symbol must be forwarded explicitly or `cookies.set()` calls are silently
30
- * dropped. Same pattern as finalizeResponse in ../middleware.ts.
31
- */
32
- const ASTRO_COOKIES_SYMBOL = Symbol.for("astro.cookies");
33
-
34
30
  /** Shape of the NDJSON snapshot emitted when the body finishes streaming. */
35
31
  export interface StreamEndSnapshot {
36
32
  route?: string;
@@ -12,6 +12,7 @@ import type { Kysely } from "kysely";
12
12
  import virtualConfig from "virtual:emdash/config";
13
13
  // @ts-ignore - virtual module
14
14
  import {
15
+ createCoalescingDialect as virtualCreateCoalescingDialect,
15
16
  createDialect as virtualCreateDialect,
16
17
  createRequestScopedDb as virtualCreateRequestScopedDb,
17
18
  } from "virtual:emdash/dialect";
@@ -59,6 +60,7 @@ import type { PublishedRef } from "../scheduled-publish.js";
59
60
  import { isMissingTableError } from "../utils/db-errors.js";
60
61
  import { createInitLock, type InitLock, initWithLock } from "../utils/init-lock.js";
61
62
  import type { EmDashConfig } from "./integration/runtime.js";
63
+ import { ASTRO_COOKIES_SYMBOL, finishScoped } from "./middleware/scoped-db.js";
62
64
  import { wrapBodyForStreamMetrics } from "./middleware/stream-end-metrics.js";
63
65
  import { prefetchLayoutData } from "./prefetch.js";
64
66
  import { createPublicPluginApiRouteHandler } from "./public-plugin-api-routes.js";
@@ -179,6 +181,10 @@ function buildDependencies(config: EmDashConfig): RuntimeDependencies {
179
181
  config,
180
182
  plugins: getPlugins(),
181
183
  createDialect: virtualCreateDialect as (config: Record<string, unknown>) => unknown,
184
+ // Optional: only batching backends (D1, DO) export this; undefined otherwise.
185
+ createCoalescingDialect: virtualCreateCoalescingDialect as
186
+ | ((config: Record<string, unknown>) => unknown)
187
+ | undefined,
182
188
  createStorage: virtualCreateStorage as ((config: Record<string, unknown>) => Storage) | null,
183
189
  createScheduler: virtualCreateScheduler as CreateSchedulerFn | null,
184
190
  sandboxEnabled: sandboxModule.sandboxEnabled as boolean,
@@ -270,16 +276,71 @@ export async function runScheduledTasks(
270
276
  const config = getConfig();
271
277
  if (!config) return { published: [] };
272
278
  const runtime = await getRuntime(config);
273
- return runtime.runScheduledTasks(options);
279
+
280
+ // Connection-backed adapters (e.g. Postgres over Hyperdrive) cannot reuse
281
+ // the per-isolate singleton from a Cron Trigger: its socket belongs to the
282
+ // request that opened it, and workerd rejects cross-event I/O. Open an
283
+ // event-scoped connection for the sweep and run the batch under it in ALS —
284
+ // the runtime's db getter, the cron executor, and plugin cron contexts all
285
+ // resolve the connection from ALS — then close it. Gated on the adapter
286
+ // being connection-backed (it exposes `close()`); stateless adapters (D1,
287
+ // Node SQLite) return null or a close-less scope and keep using the
288
+ // singleton, so their cron path is unchanged.
289
+ const scoped = createRequestScopedDb({
290
+ config: config.database?.config,
291
+ isAuthenticated: false,
292
+ // The sweep publishes and cleans up — a write workload — so a
293
+ // connection-backed adapter routes it to the primary.
294
+ isWrite: true,
295
+ cookies: NOOP_COOKIE_JAR,
296
+ url: CRON_EVENT_URL,
297
+ });
298
+ if (!scoped?.close) {
299
+ // Stateless adapter (or no per-request scoping): the singleton is safe
300
+ // outside a request. Any close-less scope created above is discarded.
301
+ return runtime.runScheduledTasks(options);
302
+ }
303
+
304
+ const parent = getRequestContext();
305
+ const ctx = parent
306
+ ? { ...parent, db: scoped.db }
307
+ : { editMode: false, db: scoped.db, metrics: createRequestMetrics(performance.now()) };
308
+ try {
309
+ return await runWithContext(ctx, () => runtime.runScheduledTasks(options));
310
+ } finally {
311
+ // Guard both so a throw in teardown can't mask the sweep result or skip
312
+ // close() and leak the connection. Mirrors closeSafely() in scoped-db.ts.
313
+ try {
314
+ scoped.commit();
315
+ } catch (error) {
316
+ console.error("[scheduled] request-scoped db commit failed:", error);
317
+ }
318
+ try {
319
+ scoped.close();
320
+ } catch (error) {
321
+ console.error("[scheduled] request-scoped db close failed:", error);
322
+ }
323
+ }
274
324
  }
275
325
 
276
326
  /**
277
- * Astro attaches AstroCookies to outgoing responses via a well-known global
278
- * symbol. Cloning a Response (`new Response(body, init)`) drops non-header
279
- * metadata, so any middleware that wraps the response must explicitly forward
280
- * this symbol or `cookies.set()` calls will be silently dropped.
327
+ * A cookie jar that reads nothing and writes nothing, for request-scoped db
328
+ * adapters invoked outside an HTTP request (the Cron Trigger sweep). Connection
329
+ * adapters like Hyperdrive ignore cookies entirely; the D1 session adapter
330
+ * reads/writes a bookmark cookie, but cron never reaches that path (it has no
331
+ * `close()`), so the no-ops are never observed.
332
+ */
333
+ const NOOP_COOKIE_JAR = {
334
+ get: () => undefined,
335
+ set: () => {},
336
+ };
337
+
338
+ /**
339
+ * Synthetic URL for the cron sweep's request-scoped db opts. Only the D1
340
+ * session adapter inspects `url` (for cookie `secure`), and cron doesn't take
341
+ * that path, so the value is never used — it exists to satisfy the contract.
281
342
  */
282
- const ASTRO_COOKIES_SYMBOL = Symbol.for("astro.cookies");
343
+ const CRON_EVENT_URL = new URL("https://cron.emdash.internal/");
283
344
 
284
345
  /**
285
346
  * Baseline security headers applied to all responses.
@@ -294,9 +355,19 @@ function finalizeResponse(
294
355
  if (astroCookies !== undefined) {
295
356
  Reflect.set(res, ASTRO_COOKIES_SYMBOL, astroCookies);
296
357
  }
297
- res.headers.set("X-Content-Type-Options", "nosniff");
298
- res.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
299
- res.headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=()");
358
+ // Set-if-absent so a host app that sets stricter values on its own routes
359
+ // wins. The middleware registers `order: 'pre'` (#1282), so on the response
360
+ // path it runs *after* host middleware; unconditional `set()` would clobber
361
+ // the host's headers on every public route (#1393). Mirrors the CSP guard.
362
+ if (!res.headers.has("X-Content-Type-Options")) {
363
+ res.headers.set("X-Content-Type-Options", "nosniff");
364
+ }
365
+ if (!res.headers.has("Referrer-Policy")) {
366
+ res.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
367
+ }
368
+ if (!res.headers.has("Permissions-Policy")) {
369
+ res.headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=()");
370
+ }
300
371
  if (!res.headers.has("Content-Security-Policy")) {
301
372
  res.headers.set("X-Frame-Options", "SAMEORIGIN");
302
373
  }
@@ -356,12 +427,12 @@ const SITEMAP_COLLECTION_RE = /^\/sitemap-[a-z][a-z0-9_]*\.xml$/;
356
427
  */
357
428
  function createRequestScopedDb(
358
429
  opts: RequestScopedDbOpts,
359
- ): { db: Kysely<Database>; commit: () => void } | null {
430
+ ): { db: Kysely<Database>; commit: () => void; close?: () => void } | null {
360
431
  if (typeof virtualCreateRequestScopedDb !== "function") return null;
361
432
  // eslint-disable-next-line typescript/no-unsafe-type-assertion -- adapter returns Kysely<unknown>; cast to Database since core owns that type
362
433
  const fn = virtualCreateRequestScopedDb as (
363
434
  o: RequestScopedDbOpts,
364
- ) => { db: Kysely<Database>; commit: () => void } | null;
435
+ ) => { db: Kysely<Database>; commit: () => void; close?: () => void } | null;
365
436
  return fn(opts);
366
437
  }
367
438
 
@@ -546,15 +617,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
546
617
  .startsWith("text/html");
547
618
  return runWithContext(ctx, async () => {
548
619
  if (acceptsHtml) after(() => prefetchLayoutData());
549
- // commit() in finally: the write reached the primary independently
550
- // of render, so the bookmark cookie must be persisted even if
551
- // render throws -- otherwise a write-then-failed-render leaves the
552
- // next request able to read pre-write state off a lagging replica.
553
- try {
554
- return await runAnon();
555
- } finally {
556
- anonScoped.commit();
557
- }
620
+ // commit() persists per-request state (e.g. the D1 bookmark cookie)
621
+ // before the response is returned, even if render throws; close()
622
+ // (connection teardown) is deferred to stream-end. See finishScoped.
623
+ return finishScoped(anonScoped, runAnon);
558
624
  });
559
625
  }
560
626
  return runAnon();
@@ -658,7 +724,17 @@ export const onRequest = defineMiddleware(async (context, next) => {
658
724
 
659
725
  // Direct access (for advanced use cases)
660
726
  storage: runtime.storage,
661
- db: runtime.db,
727
+ // Lazy getter, not an eager snapshot: `locals.emdash` is built
728
+ // before the per-request scoped db is installed in ALS, so reading
729
+ // `runtime.db` here would capture the per-isolate singleton. Routes
730
+ // access `emdash.db` later, during the request, when the scoped db
731
+ // is active. For a stateless binding (D1) the two are equivalent,
732
+ // but for a request-bound connection (pg/Hyperdrive) the singleton
733
+ // belongs to the cold-start request and reusing it from a warm
734
+ // request hangs on workerd's cross-request I/O guard.
735
+ get db() {
736
+ return runtime.db;
737
+ },
662
738
  getPublicMediaUrl: createPublicMediaUrlResolver(runtime.storage),
663
739
  hooks: runtime.hooks,
664
740
  email: runtime.email,
@@ -722,16 +798,12 @@ export const onRequest = defineMiddleware(async (context, next) => {
722
798
  const ctx = parent
723
799
  ? { ...parent, db: scoped.db }
724
800
  : { editMode: false, db: scoped.db, metrics };
725
- return runWithContext(ctx, async () => {
726
- // commit() in finally: persist the bookmark cookie even if render
727
- // throws -- the write already reached the primary, so a failed
728
- // render must not strand the next request on a stale replica read.
729
- try {
730
- return await renderAndFinalize();
731
- } finally {
732
- scoped.commit();
733
- }
734
- });
801
+ return runWithContext(ctx, () =>
802
+ // commit() persists per-request state (e.g. the D1 bookmark cookie)
803
+ // before the response returns, even if render throws; close()
804
+ // (connection teardown) is deferred to stream-end. See finishScoped.
805
+ finishScoped(scoped, renderAndFinalize),
806
+ );
735
807
  }
736
808
 
737
809
  return renderAndFinalize();
@@ -5,7 +5,10 @@
5
5
  * This page serves the EmDash admin React SPA.
6
6
  * AdminWrapper imports plugin admin modules and passes them to AdminApp.
7
7
  */
8
- import "@emdash-cms/admin/styles.css";
8
+ // `?url` keeps the admin stylesheet out of the page's CSS module graph so
9
+ // Astro's dev SSR doesn't inject it into every public route's <head>. We emit
10
+ // it as a route-scoped <link> below instead. See #1281.
11
+ import adminStylesUrl from "@emdash-cms/admin/styles.css?url";
9
12
  // Use package-qualified import so Astro generates a proper module URL
10
13
  // (relative imports resolve to absolute paths which break client hydration)
11
14
  import AdminWrapper from "emdash/routes/PluginRegistry";
@@ -28,6 +31,7 @@ const pageTitle = adminConfig?.siteName ? `${adminConfig.siteName} Admin` : "EmD
28
31
  <head>
29
32
  <meta charset="UTF-8" />
30
33
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
34
+ <link rel="stylesheet" href={adminStylesUrl} />
31
35
  <Font cssVariable="--font-emdash" />
32
36
  {adminConfig?.favicon ? (
33
37
  <link rel="icon" href={adminConfig.favicon} />
@@ -15,7 +15,7 @@ import { hashString } from "emdash";
15
15
  import { requirePerm } from "#api/authorize.js";
16
16
  import { apiSuccess, handleError, requireDb } from "#api/error.js";
17
17
  import { SchemaRegistry } from "#schema/registry.js";
18
- import { generateTypeScript } from "#schema/zod-generator.js";
18
+ import { generateTypeScript, uniqueInterfaceNames } from "#schema/zod-generator.js";
19
19
 
20
20
  export const prerender = false;
21
21
 
@@ -45,8 +45,14 @@ export const GET: APIRoute = async ({ request, locals }) => {
45
45
  const format = url.searchParams.get("format");
46
46
 
47
47
  if (format === "typescript") {
48
- // Generate TypeScript definitions
49
- const types = collectionsWithFields.map((c) => generateTypeScript(c)).join("\n\n");
48
+ // Generate TypeScript definitions. Singularizing slugs can collapse
49
+ // distinct collections onto the same interface name, so resolve names
50
+ // up front to keep every emitted interface unique (`book` + `books`
51
+ // would otherwise both emit `Book`).
52
+ const interfaceNames = uniqueInterfaceNames(collectionsWithFields);
53
+ const types = collectionsWithFields
54
+ .map((c) => generateTypeScript(c, interfaceNames.get(c.slug)))
55
+ .join("\n\n");
50
56
 
51
57
  const header = `// Generated by EmDash CLI
52
58
  // Do not edit manually - run \`emdash types\` to regenerate
@@ -153,13 +153,16 @@ export async function exportSeed(db: Kysely<Database>, withContent?: string): Pr
153
153
 
154
154
  // 7. Export content (if requested)
155
155
  if (withContent !== undefined) {
156
- const collections =
157
- withContent === "" || withContent === "true"
158
- ? null // all collections
159
- : withContent
160
- .split(",")
161
- .map((s) => s.trim())
162
- .filter(Boolean);
156
+ // Treat "all" as a synonym for the bare flag and "true". The args help
157
+ // text documents `all` as a valid value, but without this the literal
158
+ // string is read as a collection name and matches no collection (#1329).
159
+ const includeAll = withContent === "" || withContent === "true" || withContent === "all";
160
+ const collections = includeAll
161
+ ? null // all collections
162
+ : withContent
163
+ .split(",")
164
+ .map((s) => s.trim())
165
+ .filter(Boolean);
163
166
 
164
167
  seed.content = await exportContent(
165
168
  db,
@@ -372,9 +375,10 @@ async function exportTaxonomies(
372
375
  // Terms in this def's locale.
373
376
  const terms = await termRepo.findByName(def.name, { locale: def.locale });
374
377
 
375
- // id -> slug for parent resolution within this locale.
376
- const idToSlug = new Map<string, string>();
377
- for (const term of terms) idToSlug.set(term.id, term.slug);
378
+ // translation_group -> slug for parent resolution within this locale.
379
+ // `parentId` stores the parent's translation_group, not a row id.
380
+ const groupToSlug = new Map<string, string>();
381
+ for (const term of terms) groupToSlug.set(term.translationGroup ?? term.id, term.slug);
378
382
 
379
383
  // translation_group -> seed id of the anchor term.
380
384
  const termGroupToSeedId = new Map<string, string>();
@@ -393,7 +397,7 @@ async function exportTaxonomies(
393
397
  description: typeof term.data?.description === "string" ? term.data.description : undefined,
394
398
  };
395
399
 
396
- if (term.parentId) seedTerm.parent = idToSlug.get(term.parentId);
400
+ if (term.parentId) seedTerm.parent = groupToSlug.get(term.parentId);
397
401
 
398
402
  if (i18nEnabled && term.locale) {
399
403
  seedTerm.locale = term.locale;
@@ -0,0 +1,71 @@
1
+ ---
2
+ /**
3
+ * EmDash custom block override for `astro-portabletext`.
4
+ *
5
+ * Renders the same HTML as the upstream Block component (h1..h6, blockquote,
6
+ * `<p>` for `normal`) and additionally surfaces `textAlign` from the rich-text
7
+ * editor as a WordPress-style `has-text-align-{value}` class on the rendered
8
+ * paragraph or heading.
9
+ *
10
+ * `left` is the editor default and intentionally does not produce a class —
11
+ * paragraphs/headings without explicit alignment render exactly as they did
12
+ * before this fix, so existing content is byte-for-byte unchanged.
13
+ *
14
+ * Related: https://github.com/emdash-cms/emdash/issues/1201
15
+ */
16
+ import type { BlockProps } from "astro-portabletext/types";
17
+
18
+ import { textAlignClassName } from "./portable-text-text-align.js";
19
+
20
+ type TextAlignedNode = BlockProps["node"] & { textAlign?: string };
21
+ type Props = Omit<BlockProps, "node"> & { node: TextAlignedNode };
22
+
23
+ const props = Astro.props as Props;
24
+ const { node, index: _index, isInline: _isInline, ...attrs } = props;
25
+
26
+ const styleIs = (style: string) => style === node.style;
27
+
28
+ // Allowlist-based; attacker-controlled PT data cannot inject arbitrary classes.
29
+ // See `./portable-text-text-align.ts`.
30
+ const alignClass = textAlignClassName(node.textAlign);
31
+ ---
32
+
33
+ {
34
+ styleIs("h1") ? (
35
+ <h1 class={alignClass} {...attrs}>
36
+ <slot />
37
+ </h1>
38
+ ) : styleIs("h2") ? (
39
+ <h2 class={alignClass} {...attrs}>
40
+ <slot />
41
+ </h2>
42
+ ) : styleIs("h3") ? (
43
+ <h3 class={alignClass} {...attrs}>
44
+ <slot />
45
+ </h3>
46
+ ) : styleIs("h4") ? (
47
+ <h4 class={alignClass} {...attrs}>
48
+ <slot />
49
+ </h4>
50
+ ) : styleIs("h5") ? (
51
+ <h5 class={alignClass} {...attrs}>
52
+ <slot />
53
+ </h5>
54
+ ) : styleIs("h6") ? (
55
+ <h6 class={alignClass} {...attrs}>
56
+ <slot />
57
+ </h6>
58
+ ) : styleIs("blockquote") ? (
59
+ <blockquote class={alignClass} {...attrs}>
60
+ <slot />
61
+ </blockquote>
62
+ ) : styleIs("normal") ? (
63
+ <p class={alignClass} {...attrs}>
64
+ <slot />
65
+ </p>
66
+ ) : (
67
+ <p class={alignClass} {...attrs}>
68
+ <slot />
69
+ </p>
70
+ )
71
+ }
@@ -158,9 +158,8 @@ const finalAlt = alt ?? (embed?.type === "image" ? embed.alt : undefined) ?? med
158
158
  </audio>
159
159
  )}
160
160
 
161
+ {/* Custom component embeds (Mux player, etc.) require dynamic import. Placeholder until that lands. */}
161
162
  {embed?.type === "component" && (
162
- <!-- Custom component embeds (Mux player, etc.) require dynamic import -->
163
- <!-- For now, show a placeholder - full implementation TBD -->
164
163
  <div class="emdash-media-component" data-package={embed.package} data-export={embed.export}>
165
164
  <p>Custom media component: {embed.package}</p>
166
165
  </div>
@@ -38,6 +38,8 @@ export interface Props {
38
38
  displayWidth?: number;
39
39
  /** Display height for this instance (overrides original) */
40
40
  displayHeight?: number;
41
+ /** Alignment for this image instance (e.g. from a WordPress import) */
42
+ alignment?: "left" | "center" | "right" | "wide" | "full";
41
43
  };
42
44
  }
43
45
 
@@ -63,7 +65,7 @@ if (!node?.asset) {
63
65
  return null;
64
66
  }
65
67
 
66
- const { asset, alt = "", caption, width, height, displayWidth, displayHeight } = node;
68
+ const { asset, alt = "", caption, width, height, displayWidth, displayHeight, alignment } = node;
67
69
 
68
70
  // Calculate aspect ratio from original dimensions
69
71
  const aspectRatio = width && height ? width / height : undefined;
@@ -168,7 +170,7 @@ const baseStyle = aspectRatio
168
170
  const imgStyle = placeholderStyle ? `${baseStyle} ${placeholderStyle}` : baseStyle;
169
171
  ---
170
172
 
171
- <figure class="emdash-image">
173
+ <figure class:list={["emdash-image", alignment && `emdash-image--align-${alignment}`]}>
172
174
  <img
173
175
  src={src}
174
176
  srcset={srcset}
@@ -197,4 +199,36 @@ const imgStyle = placeholderStyle ? `${baseStyle} ${placeholderStyle}` : baseSty
197
199
  margin-top: 0.5rem;
198
200
  text-align: center;
199
201
  }
202
+ /* Alignment (WordPress-style). Floats wrap surrounding text; wide/full span the column. */
203
+ .emdash-image--align-left,
204
+ .emdash-image--align-right {
205
+ width: fit-content;
206
+ max-width: 50%;
207
+ }
208
+ .emdash-image--align-left {
209
+ float: left;
210
+ margin: 0 1.5rem 1rem 0;
211
+ }
212
+ .emdash-image--align-right {
213
+ float: right;
214
+ margin: 0 0 1rem 1.5rem;
215
+ }
216
+ .emdash-image--align-center {
217
+ width: fit-content;
218
+ margin-inline: auto;
219
+ }
220
+ .emdash-image--align-wide,
221
+ .emdash-image--align-full {
222
+ width: 100%;
223
+ }
224
+ /* On narrow screens, un-float left/right so the image is not cramped beside text. */
225
+ @media (max-width: 640px) {
226
+ .emdash-image--align-left,
227
+ .emdash-image--align-right {
228
+ float: none;
229
+ width: 100%;
230
+ max-width: 100%;
231
+ margin-inline: 0;
232
+ }
233
+ }
200
234
  </style>
@@ -51,6 +51,7 @@ interface PTTextBlock {
51
51
  level?: number;
52
52
  children: PTSpan[];
53
53
  markDefs?: PTMarkDef[];
54
+ textAlign?: "left" | "center" | "right" | "justify";
54
55
  }
55
56
 
56
57
  type PTBlock = PTTextBlock | { _type: string; _key: string; [key: string]: unknown };
@@ -119,12 +120,15 @@ function convertPMNode(node: PMNode): PTBlock | PTBlock[] | null {
119
120
  case "paragraph": {
120
121
  const { children, markDefs } = convertInline(node.content || []);
121
122
  if (children.length === 0) return null;
123
+ const ta = node.attrs?.textAlign;
124
+ const textAlign = ta === "center" || ta === "right" || ta === "justify" ? ta : undefined;
122
125
  return {
123
126
  _type: "block",
124
127
  _key: k(),
125
128
  style: "normal",
126
129
  children,
127
130
  markDefs: markDefs.length > 0 ? markDefs : undefined,
131
+ ...(textAlign ? { textAlign } : {}),
128
132
  };
129
133
  }
130
134
  case "heading": {
@@ -140,12 +144,15 @@ function convertPMNode(node: PMNode): PTBlock | PTBlock[] | null {
140
144
  6: "h6",
141
145
  };
142
146
  const headingStyle = headingStyles[level] ?? "h1";
147
+ const ta = node.attrs?.textAlign;
148
+ const textAlign = ta === "center" || ta === "right" || ta === "justify" ? ta : undefined;
143
149
  return {
144
150
  _type: "block",
145
151
  _key: k(),
146
152
  style: headingStyle,
147
153
  children,
148
154
  markDefs: markDefs.length > 0 ? markDefs : undefined,
155
+ ...(textAlign ? { textAlign } : {}),
149
156
  };
150
157
  }
151
158
  case "bulletList":
@@ -361,7 +368,7 @@ function portableTextToPM(blocks: PTBlock[]): JSONContent {
361
368
 
362
369
  function convertPTBlock(block: PTBlock): JSONContent | null {
363
370
  if (isPTTextBlock(block)) {
364
- const { style = "normal", children, markDefs = [] } = block;
371
+ const { style = "normal", children, markDefs = [], textAlign } = block;
365
372
  const pmContent = convertPTSpans(children, markDefs);
366
373
 
367
374
  if (style === "blockquote") {
@@ -379,12 +386,13 @@ function convertPTBlock(block: PTBlock): JSONContent | null {
379
386
  const level = parseInt(style.substring(1), 10);
380
387
  return {
381
388
  type: "heading",
382
- attrs: { level },
389
+ attrs: { level, ...(textAlign ? { textAlign } : {}) },
383
390
  content: pmContent.length > 0 ? pmContent : undefined,
384
391
  };
385
392
  }
386
393
  return {
387
394
  type: "paragraph",
395
+ attrs: textAlign ? { textAlign } : undefined,
388
396
  content: pmContent.length > 0 ? pmContent : undefined,
389
397
  };
390
398
  }