emdash 0.30.0 → 0.31.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 (387) hide show
  1. package/dist/api/route-utils.mjs +13 -13
  2. package/dist/api/schemas/index.d.mts +1 -1
  3. package/dist/api/schemas/index.mjs +3 -3
  4. package/dist/{api-b8WIiGU4.mjs → api-EibxGP-V.mjs} +23 -23
  5. package/dist/api-EibxGP-V.mjs.map +1 -0
  6. package/dist/{apply-CmIJK9j8.mjs → apply-fGHCAwWN.mjs} +26 -25
  7. package/dist/apply-fGHCAwWN.mjs.map +1 -0
  8. package/dist/astro/index.d.mts +2 -2
  9. package/dist/astro/index.mjs +1 -1
  10. package/dist/astro/middleware/auth.d.mts +2 -2
  11. package/dist/astro/middleware/auth.mjs +2 -2
  12. package/dist/astro/middleware/redirect.mjs +5 -5
  13. package/dist/astro/middleware/request-context.mjs +1 -1
  14. package/dist/astro/middleware.d.mts +2 -2
  15. package/dist/astro/middleware.mjs +139 -68
  16. package/dist/astro/middleware.mjs.map +1 -1
  17. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  18. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  19. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  20. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  21. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  22. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +7 -7
  23. package/dist/astro/routes/api/admin/byline-fields/index.mjs +7 -7
  24. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +7 -7
  25. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -14
  26. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -14
  27. package/dist/astro/routes/api/admin/bylines/index.d.mts.map +1 -1
  28. package/dist/astro/routes/api/admin/bylines/index.mjs +18 -17
  29. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  30. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
  31. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  32. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  33. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  34. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  35. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  36. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  37. package/dist/astro/routes/api/admin/media-usage/repair.mjs +10 -10
  38. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  39. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  40. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
  41. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
  42. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +34 -34
  43. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  44. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +34 -34
  45. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +34 -34
  46. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +34 -34
  47. package/dist/astro/routes/api/admin/plugins/index.mjs +34 -34
  48. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  49. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +34 -34
  50. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +34 -34
  51. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +34 -34
  52. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +34 -34
  53. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +35 -35
  54. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +34 -34
  55. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +35 -35
  56. package/dist/astro/routes/api/admin/plugins/updates.mjs +34 -34
  57. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +34 -34
  58. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  59. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +34 -34
  60. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  61. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  62. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  63. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  64. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  65. package/dist/astro/routes/api/auth/dev-bypass.mjs +4 -4
  66. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  67. package/dist/astro/routes/api/auth/invite/complete.mjs +6 -6
  68. package/dist/astro/routes/api/auth/invite/index.mjs +6 -6
  69. package/dist/astro/routes/api/auth/invite/register-options.mjs +6 -6
  70. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  71. package/dist/astro/routes/api/auth/magic-link/send.mjs +7 -7
  72. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  73. package/dist/astro/routes/api/auth/me.mjs +7 -7
  74. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  75. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  76. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  77. package/dist/astro/routes/api/auth/passkey/options.mjs +7 -7
  78. package/dist/astro/routes/api/auth/passkey/register/options.mjs +6 -6
  79. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
  80. package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
  81. package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
  82. package/dist/astro/routes/api/auth/signup/request.mjs +7 -7
  83. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  84. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +10 -10
  85. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +10 -10
  86. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  88. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  89. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
  91. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +7 -7
  92. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +14 -14
  93. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +14 -14
  94. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  95. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  96. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +7 -7
  97. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
  98. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  99. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  100. package/dist/astro/routes/api/content/_collection_/_id_.mjs +7 -7
  101. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/index.mjs +7 -7
  103. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  104. package/dist/astro/routes/api/dashboard.mjs +7 -7
  105. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  106. package/dist/astro/routes/api/import/probe.mjs +12 -12
  107. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  108. package/dist/astro/routes/api/import/wordpress/execute.d.mts +2 -2
  109. package/dist/astro/routes/api/import/wordpress/execute.mjs +11 -11
  110. package/dist/astro/routes/api/import/wordpress/media.d.mts +2 -2
  111. package/dist/astro/routes/api/import/wordpress/media.mjs +7 -7
  112. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  113. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +2 -2
  114. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +9 -9
  115. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +12 -12
  116. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +2 -2
  117. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +19 -19
  118. package/dist/astro/routes/api/manifest.mjs +3 -3
  119. package/dist/astro/routes/api/mcp.mjs +39 -39
  120. package/dist/astro/routes/api/media/_id_/confirm.mjs +7 -7
  121. package/dist/astro/routes/api/media/_id_/usage.mjs +11 -11
  122. package/dist/astro/routes/api/media/_id_.mjs +10 -10
  123. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  124. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  125. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  126. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  127. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  128. package/dist/astro/routes/api/media.mjs +10 -10
  129. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  130. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  131. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  132. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  133. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  134. package/dist/astro/routes/api/menus/index.mjs +8 -8
  135. package/dist/astro/routes/api/oauth/authorize.d.mts.map +1 -1
  136. package/dist/astro/routes/api/oauth/authorize.mjs +23 -9
  137. package/dist/astro/routes/api/oauth/authorize.mjs.map +1 -1
  138. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  139. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  140. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  141. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  142. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  143. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  144. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  145. package/dist/astro/routes/api/openapi.json.mjs +4 -4
  146. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  147. package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
  148. package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
  149. package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
  150. package/dist/astro/routes/api/redirects/index.mjs +11 -11
  151. package/dist/astro/routes/api/relations/_id_/index.mjs +14 -14
  152. package/dist/astro/routes/api/relations/_id_/translations.mjs +10 -10
  153. package/dist/astro/routes/api/relations/index.mjs +14 -14
  154. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  155. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  156. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +34 -34
  157. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +34 -34
  158. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +34 -34
  159. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +34 -34
  160. package/dist/astro/routes/api/schema/collections/index.mjs +34 -34
  161. package/dist/astro/routes/api/schema/index.mjs +8 -8
  162. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +34 -34
  163. package/dist/astro/routes/api/schema/orphans/index.mjs +34 -34
  164. package/dist/astro/routes/api/search/enable.mjs +11 -11
  165. package/dist/astro/routes/api/search/index.mjs +10 -10
  166. package/dist/astro/routes/api/search/rebuild.mjs +11 -11
  167. package/dist/astro/routes/api/search/stats.mjs +7 -7
  168. package/dist/astro/routes/api/search/suggest.mjs +10 -10
  169. package/dist/astro/routes/api/sections/_slug_.mjs +10 -10
  170. package/dist/astro/routes/api/sections/index.mjs +10 -10
  171. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +4 -4
  172. package/dist/astro/routes/api/settings/backups/archives/index.mjs +4 -4
  173. package/dist/astro/routes/api/settings/backups/export.mjs +4 -4
  174. package/dist/astro/routes/api/settings/backups/index.mjs +5 -5
  175. package/dist/astro/routes/api/settings/email.mjs +4 -4
  176. package/dist/astro/routes/api/settings.mjs +12 -12
  177. package/dist/astro/routes/api/setup/admin-verify.mjs +6 -6
  178. package/dist/astro/routes/api/setup/admin.mjs +6 -6
  179. package/dist/astro/routes/api/setup/dev-bypass.mjs +18 -18
  180. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  181. package/dist/astro/routes/api/setup/index.mjs +22 -22
  182. package/dist/astro/routes/api/setup/status.mjs +3 -3
  183. package/dist/astro/routes/api/snapshot.mjs +4 -4
  184. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
  185. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
  186. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
  187. package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
  188. package/dist/astro/routes/api/themes/preview.mjs +4 -4
  189. package/dist/astro/routes/api/typegen.mjs +5 -5
  190. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  191. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +10 -10
  192. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +10 -10
  193. package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -6
  194. package/dist/astro/routes/api/widget-areas/index.mjs +10 -10
  195. package/dist/astro/routes/api/widget-components.mjs +2 -2
  196. package/dist/astro/routes/robots.txt.mjs +5 -5
  197. package/dist/astro/routes/sitemap-_collection_.xml.mjs +7 -7
  198. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  199. package/dist/astro/types.d.mts +2 -2
  200. package/dist/{authorize-WxsiePzy.mjs → authorize-6WPZa1xZ.mjs} +2 -2
  201. package/dist/{authorize-WxsiePzy.mjs.map → authorize-6WPZa1xZ.mjs.map} +1 -1
  202. package/dist/{backup-S8kFWtwD.mjs → backup-Qr9PaGUo.mjs} +2 -2
  203. package/dist/{backup-S8kFWtwD.mjs.map → backup-Qr9PaGUo.mjs.map} +1 -1
  204. package/dist/{byline-XEjchwzZ.mjs → byline-BTOxUlFF.mjs} +6 -6
  205. package/dist/{byline-XEjchwzZ.mjs.map → byline-BTOxUlFF.mjs.map} +1 -1
  206. package/dist/{bylines-czseViYo.mjs → bylines-FyZu6TbJ.mjs} +6 -6
  207. package/dist/{bylines-czseViYo.mjs.map → bylines-FyZu6TbJ.mjs.map} +1 -1
  208. package/dist/{bylines-BJbT4gKS.mjs → bylines-oVN0iPwX.mjs} +12 -8
  209. package/dist/bylines-oVN0iPwX.mjs.map +1 -0
  210. package/dist/{cache-CGCd6AVM.mjs → cache-DDwy3JDW.mjs} +2 -2
  211. package/dist/{cache-CGCd6AVM.mjs.map → cache-DDwy3JDW.mjs.map} +1 -1
  212. package/dist/{chunks-BxXyunY-.mjs → chunks-Bq-5DPrD.mjs} +2 -2
  213. package/dist/{chunks-BxXyunY-.mjs.map → chunks-Bq-5DPrD.mjs.map} +1 -1
  214. package/dist/cli/index.mjs +18 -18
  215. package/dist/{comment-DPT0WKyd.mjs → comment-B9cUud8H.mjs} +2 -2
  216. package/dist/{comment-DPT0WKyd.mjs.map → comment-B9cUud8H.mjs.map} +1 -1
  217. package/dist/{comment-reaction-C65MldIB.mjs → comment-reaction-VUXO981F.mjs} +2 -2
  218. package/dist/{comment-reaction-C65MldIB.mjs.map → comment-reaction-VUXO981F.mjs.map} +1 -1
  219. package/dist/{comments-Bz6sCbgD.mjs → comments-PXj1E9Si.mjs} +3 -3
  220. package/dist/{comments-Bz6sCbgD.mjs.map → comments-PXj1E9Si.mjs.map} +1 -1
  221. package/dist/{config-CVssduLe.mjs → config-B6b_BobR.mjs} +6 -2
  222. package/dist/config-B6b_BobR.mjs.map +1 -0
  223. package/dist/{content-Ci04z2z-.mjs → content-BC43pICM.mjs} +4 -4
  224. package/dist/{content-Ci04z2z-.mjs.map → content-BC43pICM.mjs.map} +1 -1
  225. package/dist/{content-refresh-D4khvC0R.mjs → content-refresh-CPMSauD3.mjs} +4 -4
  226. package/dist/{content-refresh-D4khvC0R.mjs.map → content-refresh-CPMSauD3.mjs.map} +1 -1
  227. package/dist/{context-B6hc7zJL.mjs → context-DOurRIrb.mjs} +10 -10
  228. package/dist/{context-B6hc7zJL.mjs.map → context-DOurRIrb.mjs.map} +1 -1
  229. package/dist/{dashboard-C5NkXFbi.mjs → dashboard-CBm4zhc3.mjs} +4 -4
  230. package/dist/{dashboard-C5NkXFbi.mjs.map → dashboard-CBm4zhc3.mjs.map} +1 -1
  231. package/dist/database/pg-migration-lock.d.mts.map +1 -1
  232. package/dist/database/pg-migration-lock.mjs +1 -4
  233. package/dist/database/pg-migration-lock.mjs.map +1 -1
  234. package/dist/db/index.mjs +2 -2
  235. package/dist/{dialect-helpers-HxEQGl_1.mjs → dialect-helpers-BtOwo2us.mjs} +4 -4
  236. package/dist/dialect-helpers-BtOwo2us.mjs.map +1 -0
  237. package/dist/{error-CEGF6UZb.mjs → error-DJ98TRbQ.mjs} +2 -2
  238. package/dist/{error-CEGF6UZb.mjs.map → error-DJ98TRbQ.mjs.map} +1 -1
  239. package/dist/{field-defs-cache-DvmlgP-D.mjs → field-defs-cache-KfNJ_qLQ.mjs} +2 -2
  240. package/dist/{field-defs-cache-DvmlgP-D.mjs.map → field-defs-cache-KfNJ_qLQ.mjs.map} +1 -1
  241. package/dist/{fts-manager-DzqIBrrW.mjs → fts-manager-hyCU9OOp.mjs} +3 -3
  242. package/dist/{fts-manager-DzqIBrrW.mjs.map → fts-manager-hyCU9OOp.mjs.map} +1 -1
  243. package/dist/{hreflang-db2Tpb2o.mjs → hreflang-CbYeCzl9.mjs} +7 -7
  244. package/dist/{hreflang-db2Tpb2o.mjs.map → hreflang-CbYeCzl9.mjs.map} +1 -1
  245. package/dist/{import-Dmkm8S1W.mjs → import-oDYCgNYQ.mjs} +2 -2
  246. package/dist/{import-Dmkm8S1W.mjs.map → import-oDYCgNYQ.mjs.map} +1 -1
  247. package/dist/{index-wYnC6u4o.d.mts → index-BcEH_FfS.d.mts} +33 -2
  248. package/dist/index-BcEH_FfS.d.mts.map +1 -0
  249. package/dist/index.d.mts +2 -2
  250. package/dist/index.mjs +48 -48
  251. package/dist/{load-Cx27ki1l.mjs → load-Dg5gtWma.mjs} +2 -2
  252. package/dist/{load-Cx27ki1l.mjs.map → load-Dg5gtWma.mjs.map} +1 -1
  253. package/dist/{loader-Be3ouI5L.mjs → loader-D4rigvjR.mjs} +4 -4
  254. package/dist/{loader-Be3ouI5L.mjs.map → loader-D4rigvjR.mjs.map} +1 -1
  255. package/dist/media/local-runtime.d.mts +2 -2
  256. package/dist/media/local-runtime.mjs +5 -5
  257. package/dist/{media-kIV1IxFf.mjs → media-BJ6SRY3g.mjs} +8 -4
  258. package/dist/media-BJ6SRY3g.mjs.map +1 -0
  259. package/dist/{media-BjhhENaJ.mjs → media-its1WLYv.mjs} +2 -2
  260. package/dist/{media-BjhhENaJ.mjs.map → media-its1WLYv.mjs.map} +1 -1
  261. package/dist/{media-upload-D2wk3EIt.mjs → media-upload-DNu3QySc.mjs} +4 -4
  262. package/dist/{media-upload-D2wk3EIt.mjs.map → media-upload-DNu3QySc.mjs.map} +1 -1
  263. package/dist/{media-usage-CljdO1mc.mjs → media-usage-DPQ3K4xB.mjs} +6 -6
  264. package/dist/{media-usage-CljdO1mc.mjs.map → media-usage-DPQ3K4xB.mjs.map} +1 -1
  265. package/dist/{menus-CZyG6rvx.mjs → menus-BMGzWyWH.mjs} +15 -8
  266. package/dist/menus-BMGzWyWH.mjs.map +1 -0
  267. package/dist/{menus-BcyElFtI.mjs → menus-CPf_SW7u.mjs} +86 -8
  268. package/dist/menus-CPf_SW7u.mjs.map +1 -0
  269. package/dist/{parse-C_-6klII.mjs → parse-53zyxlpv.mjs} +2 -2
  270. package/dist/{parse-C_-6klII.mjs.map → parse-53zyxlpv.mjs.map} +1 -1
  271. package/dist/plugin-utils.d.mts +2 -2
  272. package/dist/plugins/adapt-sandbox-entry.d.mts +2 -2
  273. package/dist/{query-DR73ZNfm.mjs → query-BbJQ-dGg.mjs} +14 -14
  274. package/dist/{query-DR73ZNfm.mjs.map → query-BbJQ-dGg.mjs.map} +1 -1
  275. package/dist/{query-Di7DOmPV.mjs → query-DdFGHzQF.mjs} +3 -3
  276. package/dist/{query-Di7DOmPV.mjs.map → query-DdFGHzQF.mjs.map} +1 -1
  277. package/dist/{rate-limit-CMj83JNW.mjs → rate-limit-D3IEiUi2.mjs} +2 -2
  278. package/dist/{rate-limit-CMj83JNW.mjs.map → rate-limit-D3IEiUi2.mjs.map} +1 -1
  279. package/dist/{redirect-CgLPYflR.mjs → redirect-BoUac4NT.mjs} +3 -3
  280. package/dist/{redirect-CgLPYflR.mjs.map → redirect-BoUac4NT.mjs.map} +1 -1
  281. package/dist/{redirects-B7t9bKsL.mjs → redirects-ChbpqKiS.mjs} +3 -3
  282. package/dist/{redirects-B7t9bKsL.mjs.map → redirects-ChbpqKiS.mjs.map} +1 -1
  283. package/dist/{redirects-BBq3n2Rj.mjs → redirects-D457qwt0.mjs} +8 -8
  284. package/dist/redirects-D457qwt0.mjs.map +1 -0
  285. package/dist/{registry-FV15nLge.mjs → registry-y21kjhfG.mjs} +6 -6
  286. package/dist/{registry-FV15nLge.mjs.map → registry-y21kjhfG.mjs.map} +1 -1
  287. package/dist/{relation-9M9j_UWo.mjs → relation-Du0r04E0.mjs} +3 -3
  288. package/dist/{relation-9M9j_UWo.mjs.map → relation-Du0r04E0.mjs.map} +1 -1
  289. package/dist/{relations-Bh8jlFsU.d.mts → relations-BKybBoOX.d.mts} +12 -8
  290. package/dist/relations-BKybBoOX.d.mts.map +1 -0
  291. package/dist/{relations-5_avdrN_.mjs → relations-CZMpBqEd.mjs} +3 -3
  292. package/dist/{relations-5_avdrN_.mjs.map → relations-CZMpBqEd.mjs.map} +1 -1
  293. package/dist/{relations-BKvO39-9.mjs → relations-DtX1UMBH.mjs} +13 -7
  294. package/dist/relations-DtX1UMBH.mjs.map +1 -0
  295. package/dist/{resolve-Cd9dzclN.mjs → resolve-DFr4kq72.mjs} +2 -2
  296. package/dist/{resolve-Cd9dzclN.mjs.map → resolve-DFr4kq72.mjs.map} +1 -1
  297. package/dist/{runner-DfnZ5eUr.mjs → runner-BdeLEgad.mjs} +3 -3
  298. package/dist/{runner-DfnZ5eUr.mjs.map → runner-BdeLEgad.mjs.map} +1 -1
  299. package/dist/runtime.d.mts +2 -2
  300. package/dist/runtime.mjs +3 -3
  301. package/dist/{schema-BXxlHeAf.mjs → schema-BH8P8vaC.mjs} +3 -3
  302. package/dist/{schema-BXxlHeAf.mjs.map → schema-BH8P8vaC.mjs.map} +1 -1
  303. package/dist/{scopes-Bl4IwHA-.mjs → scopes-DTIkIaGA.mjs} +2 -2
  304. package/dist/{scopes-Bl4IwHA-.mjs.map → scopes-DTIkIaGA.mjs.map} +1 -1
  305. package/dist/{search-Bff-7jFt.mjs → search-D-Yhpmvs.mjs} +8 -7
  306. package/dist/search-D-Yhpmvs.mjs.map +1 -0
  307. package/dist/{secrets-CSwQIl4q.mjs → secrets-CQ-69kF9.mjs} +23 -17
  308. package/dist/secrets-CQ-69kF9.mjs.map +1 -0
  309. package/dist/{sections-CwW4s1al.mjs → sections-CzVBAKjw.mjs} +3 -3
  310. package/dist/{sections-CwW4s1al.mjs.map → sections-CzVBAKjw.mjs.map} +1 -1
  311. package/dist/seed/index.mjs +16 -16
  312. package/dist/seo/index.mjs +12 -12
  313. package/dist/{seo-2cmFHZns.mjs → seo-kJzVAmmu.mjs} +2 -2
  314. package/dist/{seo-2cmFHZns.mjs.map → seo-kJzVAmmu.mjs.map} +1 -1
  315. package/dist/{service-DkGTWGIi.mjs → service-CQnc276a.mjs} +2 -2
  316. package/dist/{service-DkGTWGIi.mjs.map → service-CQnc276a.mjs.map} +1 -1
  317. package/dist/{settings-CpA4lQFt.mjs → settings-C2PaBS49.mjs} +3 -3
  318. package/dist/{settings-CpA4lQFt.mjs.map → settings-C2PaBS49.mjs.map} +1 -1
  319. package/dist/{settings-C4s8hFQm.mjs → settings-Dtc0DU31.mjs} +3 -3
  320. package/dist/{settings-C4s8hFQm.mjs.map → settings-Dtc0DU31.mjs.map} +1 -1
  321. package/dist/{taxonomies-DjSKBZpq.mjs → taxonomies-HI9fnTdp.mjs} +7 -7
  322. package/dist/{taxonomies-DjSKBZpq.mjs.map → taxonomies-HI9fnTdp.mjs.map} +1 -1
  323. package/dist/{taxonomies-Ce49uIzY.mjs → taxonomies-rMeBzOjX.mjs} +26 -17
  324. package/dist/taxonomies-rMeBzOjX.mjs.map +1 -0
  325. package/dist/{taxonomy-DfVooU4W.mjs → taxonomy-0mCI25HN.mjs} +3 -3
  326. package/dist/{taxonomy-DfVooU4W.mjs.map → taxonomy-0mCI25HN.mjs.map} +1 -1
  327. package/dist/{types-D1iJ3DpO.mjs → types-CrRUFdej.mjs} +2 -2
  328. package/dist/{types-D1iJ3DpO.mjs.map → types-CrRUFdej.mjs.map} +1 -1
  329. package/dist/{user-Bh-L1qo6.mjs → user-aTXqmXBz.mjs} +3 -3
  330. package/dist/{user-Bh-L1qo6.mjs.map → user-aTXqmXBz.mjs.map} +1 -1
  331. package/dist/{validate-V9nCwq_-.mjs → validate-CDT1jl6_.mjs} +11 -6
  332. package/dist/validate-CDT1jl6_.mjs.map +1 -0
  333. package/dist/validate-DULfgpJP.d.mts.map +1 -1
  334. package/dist/{validation-BsVUJfsP.mjs → validation-DlD0E35W.mjs} +8 -8
  335. package/dist/{validation-BsVUJfsP.mjs.map → validation-DlD0E35W.mjs.map} +1 -1
  336. package/dist/version-Ci6bw6YK.mjs +7 -0
  337. package/dist/{version-907opKac.mjs.map → version-Ci6bw6YK.mjs.map} +1 -1
  338. package/dist/{widgets-DGv1Z04V.mjs → widgets-5fipNkMT.mjs} +2 -2
  339. package/dist/{widgets-DGv1Z04V.mjs.map → widgets-5fipNkMT.mjs.map} +1 -1
  340. package/dist/{wxr-taxonomies-Cs1N6O8z.mjs → wxr-taxonomies-BmUnBNgz.mjs} +4 -4
  341. package/dist/{wxr-taxonomies-Cs1N6O8z.mjs.map → wxr-taxonomies-BmUnBNgz.mjs.map} +1 -1
  342. package/dist/{zod-generator-B5prQ5M4.mjs → zod-generator-CwkHWz4f.mjs} +2 -2
  343. package/dist/{zod-generator-B5prQ5M4.mjs.map → zod-generator-CwkHWz4f.mjs.map} +1 -1
  344. package/package.json +5 -5
  345. package/src/api/handlers/bylines.ts +5 -4
  346. package/src/api/handlers/content.ts +25 -7
  347. package/src/api/handlers/menus.ts +11 -6
  348. package/src/api/handlers/relations.ts +7 -2
  349. package/src/api/handlers/taxonomies.ts +22 -14
  350. package/src/api/schemas/bylines.ts +4 -4
  351. package/src/api/schemas/common.ts +7 -6
  352. package/src/api/schemas/menus.ts +2 -1
  353. package/src/api/schemas/relations.ts +3 -1
  354. package/src/api/schemas/taxonomies.ts +4 -2
  355. package/src/astro/routes/api/admin/bylines/index.ts +5 -8
  356. package/src/astro/routes/api/oauth/authorize.ts +42 -10
  357. package/src/components/Gallery.astro +141 -26
  358. package/src/config/secrets.ts +25 -19
  359. package/src/content/converters/gallery.ts +61 -0
  360. package/src/content/converters/portable-text-to-prosemirror.ts +19 -0
  361. package/src/content/converters/prosemirror-to-portable-text.ts +18 -0
  362. package/src/content/converters/types.ts +36 -0
  363. package/src/database/dialect-helpers.ts +3 -4
  364. package/src/database/pg-migration-lock.ts +1 -4
  365. package/src/emdash-runtime.ts +55 -17
  366. package/src/i18n/config.ts +8 -0
  367. package/src/i18n/repair-locale-casing.ts +51 -0
  368. package/src/search/query.ts +3 -2
  369. package/src/seed/apply.ts +9 -7
  370. package/src/seed/validate.ts +15 -4
  371. package/dist/api-b8WIiGU4.mjs.map +0 -1
  372. package/dist/apply-CmIJK9j8.mjs.map +0 -1
  373. package/dist/bylines-BJbT4gKS.mjs.map +0 -1
  374. package/dist/config-CVssduLe.mjs.map +0 -1
  375. package/dist/dialect-helpers-HxEQGl_1.mjs.map +0 -1
  376. package/dist/index-wYnC6u4o.d.mts.map +0 -1
  377. package/dist/media-kIV1IxFf.mjs.map +0 -1
  378. package/dist/menus-BcyElFtI.mjs.map +0 -1
  379. package/dist/menus-CZyG6rvx.mjs.map +0 -1
  380. package/dist/redirects-BBq3n2Rj.mjs.map +0 -1
  381. package/dist/relations-BKvO39-9.mjs.map +0 -1
  382. package/dist/relations-Bh8jlFsU.d.mts.map +0 -1
  383. package/dist/search-Bff-7jFt.mjs.map +0 -1
  384. package/dist/secrets-CSwQIl4q.mjs.map +0 -1
  385. package/dist/taxonomies-Ce49uIzY.mjs.map +0 -1
  386. package/dist/validate-V9nCwq_-.mjs.map +0 -1
  387. package/dist/version-907opKac.mjs +0 -7
@@ -69,11 +69,16 @@ function getCsrfCookie(request: Request): string | null {
69
69
 
70
70
  const SCOPE_LABELS: Record<string, string> = {
71
71
  "content:read": "Read content (posts, pages, etc.)",
72
- "content:write": "Create, edit, and delete content",
72
+ "content:write": "Create, edit, and delete content; manage menus and taxonomies",
73
73
  "media:read": "View media files",
74
74
  "media:write": "Upload and manage media files",
75
75
  "schema:read": "View collection schemas",
76
76
  "schema:write": "Create and modify collection schemas",
77
+ "taxonomies:manage": "Manage taxonomies",
78
+ "menus:manage": "Manage navigation menus",
79
+ "settings:read": "View site settings",
80
+ "settings:manage": "Modify site settings",
81
+ "mcp:tools": "Use MCP tools from all enabled plugins",
77
82
  admin: "Full administrative access",
78
83
  };
79
84
 
@@ -141,7 +146,9 @@ export const GET: APIRoute = async ({ url, request, locals }) => {
141
146
  }
142
147
 
143
148
  // Parse and validate scopes
144
- const requestedScopes = (scope ?? "").split(" ").filter(Boolean).filter(isValidScope);
149
+ const requestedScopes = [
150
+ ...new Set((scope ?? "").split(" ").filter(Boolean).filter(isValidScope)),
151
+ ];
145
152
 
146
153
  if (requestedScopes.length === 0) {
147
154
  return new Response(renderErrorPage("No valid scopes requested."), {
@@ -201,6 +208,21 @@ export const POST: APIRoute = async ({ request, locals }) => {
201
208
  const v = formData.get(name);
202
209
  return typeof v === "string" ? v : fallback;
203
210
  };
211
+ const requestedScopes = new Set(
212
+ (new URL(request.url).searchParams.get("scope") ?? "")
213
+ .split(" ")
214
+ .filter(Boolean)
215
+ .filter(isValidScope),
216
+ );
217
+ const selectedScopes = [
218
+ ...new Set(
219
+ formData
220
+ .getAll("scope")
221
+ .filter((value): value is string => typeof value === "string")
222
+ .filter(isValidScope)
223
+ .filter((scope) => requestedScopes.has(scope)),
224
+ ),
225
+ ];
204
226
 
205
227
  // SEC-18: Validate CSRF token (double-submit cookie pattern).
206
228
  // The form includes a hidden csrf_token field; the cookie has the same value.
@@ -285,7 +307,7 @@ export const POST: APIRoute = async ({ request, locals }) => {
285
307
  response_type: field("response_type", "code"),
286
308
  client_id: field("client_id"),
287
309
  redirect_uri: redirectUri,
288
- scope: field("scope"),
310
+ scope: selectedScopes.join(" "),
289
311
  state,
290
312
  code_challenge: field("code_challenge"),
291
313
  code_challenge_method: field("code_challenge_method", "S256"),
@@ -294,12 +316,16 @@ export const POST: APIRoute = async ({ request, locals }) => {
294
316
 
295
317
  if (!result.success) {
296
318
  const errMsg = result.error?.message ?? "Authorization failed";
319
+ const invalidScope = result.error?.code === "INVALID_SCOPE";
297
320
  // On error, redirect back with error params — use generic description to avoid
298
321
  // leaking internal error details to the (already-validated) redirect target
299
322
  try {
300
323
  const errorUrl = new URL(redirectUri);
301
- errorUrl.searchParams.set("error", "server_error");
302
- errorUrl.searchParams.set("error_description", "Authorization failed");
324
+ errorUrl.searchParams.set("error", invalidScope ? "invalid_scope" : "server_error");
325
+ errorUrl.searchParams.set(
326
+ "error_description",
327
+ invalidScope ? "No selected permission can be granted" : "Authorization failed",
328
+ );
303
329
  if (state) errorUrl.searchParams.set("state", state);
304
330
  return Response.redirect(errorUrl.toString(), 302);
305
331
  } catch {
@@ -331,8 +357,8 @@ function renderConsentPage(params: {
331
357
  }): string {
332
358
  const scopeList = params.scopes
333
359
  .map((s) => {
334
- const label = SCOPE_LABELS[s] ?? s;
335
- return `<li>${escapeHtml(label)}</li>`;
360
+ const label = SCOPE_LABELS[s] ?? getPluginScopeLabel(s) ?? s;
361
+ return `<li><label><input type="checkbox" name="scope" value="${escapeHtml(s)}" checked><span>${escapeHtml(label)}</span></label></li>`;
336
362
  })
337
363
  .join("\n");
338
364
 
@@ -353,6 +379,8 @@ function renderConsentPage(params: {
353
379
  ul { list-style: none; margin-bottom: 1.5rem; }
354
380
  li { padding: 0.5rem 0; border-bottom: 1px solid #262626; font-size: 0.875rem; }
355
381
  li:last-child { border-bottom: none; }
382
+ label { display: flex; align-items: flex-start; gap: 0.625rem; cursor: pointer; }
383
+ input[type="checkbox"] { margin-top: 0.125rem; accent-color: #2563eb; }
356
384
  .actions { display: flex; gap: 0.75rem; }
357
385
  button { flex: 1; padding: 0.625rem 1rem; border-radius: 8px; border: none; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
358
386
  .approve { background: #2563eb; color: white; }
@@ -366,14 +394,13 @@ function renderConsentPage(params: {
366
394
  <h1>Authorize Application</h1>
367
395
  <p class="client-id">${escapeHtml(params.clientId)}</p>
368
396
  <p class="user">Signed in as <strong>${escapeHtml(params.userName)}</strong></p>
369
- <h2>Permissions requested</h2>
370
- <ul>${scopeList}</ul>
371
397
  <form method="POST">
398
+ <h2>Permissions requested</h2>
399
+ <ul>${scopeList}</ul>
372
400
  <input type="hidden" name="csrf_token" value="${escapeHtml(params.csrfToken)}">
373
401
  <input type="hidden" name="response_type" value="${escapeHtml(params.responseType)}">
374
402
  <input type="hidden" name="client_id" value="${escapeHtml(params.clientId)}">
375
403
  <input type="hidden" name="redirect_uri" value="${escapeHtml(params.redirectUri)}">
376
- <input type="hidden" name="scope" value="${escapeHtml(params.scopes.join(" "))}">
377
404
  <input type="hidden" name="state" value="${escapeHtml(params.state)}">
378
405
  <input type="hidden" name="code_challenge" value="${escapeHtml(params.codeChallenge)}">
379
406
  <input type="hidden" name="code_challenge_method" value="${escapeHtml(params.codeChallengeMethod)}">
@@ -388,6 +415,11 @@ function renderConsentPage(params: {
388
415
  </html>`;
389
416
  }
390
417
 
418
+ function getPluginScopeLabel(scope: string): string | null {
419
+ const pluginId = scope.startsWith("mcp:tools:") ? scope.slice("mcp:tools:".length) : "";
420
+ return pluginId ? `Use MCP tools from ${pluginId}` : null;
421
+ }
422
+
391
423
  function renderErrorPage(message: string): string {
392
424
  return `<!DOCTYPE html>
393
425
  <html lang="en">
@@ -2,11 +2,17 @@
2
2
  /**
3
3
  * Portable Text Gallery block component
4
4
  *
5
- * Renders image galleries from WordPress imports.
6
- * Uses Astro's Image component for optimization when dimensions are available.
5
+ * Renders image galleries from WordPress imports and EmDash media.
6
+ * Uses the provider's getEmbed/getSrc functions for external media (mirrors
7
+ * Image.astro), falling back to Astro's Image component when dimensions are
8
+ * available for locally-stored media.
7
9
  */
8
10
  import { Image as AstroImage } from "astro:assets";
11
+ import type { ImageEmbed } from "../media/types.js";
12
+ import { getMediaProvider } from "../media/provider-loader.js";
9
13
  import { buildRenderMediaUrl } from "../media/url.js";
14
+ import { toAbsoluteMediaUrl, RESPONSIVE_BREAKPOINTS } from "../media/responsive.js";
15
+ import { getPublicOrigin } from "../api/public-url.js";
10
16
 
11
17
  export interface Props {
12
18
  node: {
@@ -18,50 +24,159 @@ export interface Props {
18
24
  asset: {
19
25
  _ref: string;
20
26
  url?: string;
27
+ /** Provider ID for external media (e.g., "cloudflare-images") */
28
+ provider?: string;
21
29
  };
22
30
  alt?: string;
23
31
  caption?: string;
24
32
  width?: number;
25
33
  height?: number;
34
+ /** LQIP blurhash placeholder (images only) */
35
+ blurhash?: string;
36
+ /** LQIP dominant-color placeholder, as a CSS color (images only) */
37
+ dominantColor?: string;
26
38
  }>;
27
39
  columns?: number;
28
40
  };
41
+ /** Render the LQIP placeholder as an inline background. Disable for strict CSP. */
42
+ placeholder?: boolean;
29
43
  }
30
44
 
31
- const { node } = Astro.props;
45
+ /**
46
+ * Generate srcset using provider's getSrc function
47
+ */
48
+ function generateSrcset(
49
+ getSrc: NonNullable<ImageEmbed["getSrc"]>,
50
+ maxWidth: number,
51
+ aspectRatio?: number,
52
+ ): string {
53
+ return RESPONSIVE_BREAKPOINTS.filter((w) => w <= maxWidth * 2)
54
+ .map((w) => {
55
+ const h = aspectRatio ? Math.round(w / aspectRatio) : undefined;
56
+ return `${getSrc({ width: w, height: h })} ${w}w`;
57
+ })
58
+ .join(", ");
59
+ }
60
+
61
+ const { node, placeholder = true } = Astro.props;
32
62
  const images = node?.images ?? [];
33
63
  const columns = node?.columns ?? 3;
34
64
 
35
65
  if (!images.length) {
36
66
  return null;
37
67
  }
68
+
69
+ // Resolve provider embeds up front (async), keeping the template's `.map()`
70
+ // synchronous over the resolved data — mirrors Image.astro's per-image logic.
71
+ const resolvedImages = await Promise.all(
72
+ images.map(async (image) => {
73
+ const { asset, alt = "", width, height } = image;
74
+ const aspectRatio = width && height ? width / height : undefined;
75
+
76
+ let src = "";
77
+ let srcset: string | undefined;
78
+ let sizes: string | undefined;
79
+ let astroImageSrc = "";
80
+ const providerId = asset.provider;
81
+
82
+ if (providerId && providerId !== "local") {
83
+ const provider = await getMediaProvider(providerId);
84
+ if (provider) {
85
+ try {
86
+ const mediaValue = {
87
+ provider: providerId,
88
+ id: asset._ref,
89
+ width,
90
+ height,
91
+ alt,
92
+ };
93
+ const result = provider.getEmbed(mediaValue, { width, height });
94
+ const embed = result instanceof Promise ? await result : result;
95
+ if (embed.type === "image") {
96
+ src = embed.src;
97
+
98
+ if (embed.getSrc) {
99
+ const maxWidth = width || 1200;
100
+ srcset = generateSrcset(embed.getSrc, maxWidth, aspectRatio);
101
+ sizes = width ? `(min-width: ${width}px) ${width}px, 100vw` : "100vw";
102
+ }
103
+ }
104
+ } catch (error) {
105
+ console.warn(`Failed to get embed for image ${asset._ref}:`, error);
106
+ }
107
+ }
108
+ }
109
+
110
+ // Fallback for local provider. `asset.url` carries the storage key with
111
+ // extension when present; `asset._ref` is a bare ULID that only the
112
+ // internal `/file/{id}` route can resolve. `buildRenderMediaUrl` picks
113
+ // the right shape.
114
+ if (!src) {
115
+ src = buildRenderMediaUrl(Astro.locals.emdash?.getPublicMediaUrl, {
116
+ url: asset.url,
117
+ id: asset._ref,
118
+ });
119
+ if (width && height) {
120
+ astroImageSrc = toAbsoluteMediaUrl(
121
+ src,
122
+ getPublicOrigin(Astro.url, Astro.locals.emdash?.config),
123
+ );
124
+ }
125
+ }
126
+
127
+ // Prefer blurhash over dominant color for the LQIP placeholder, like
128
+ // Image.astro.
129
+ let placeholderStyle = "";
130
+ if (placeholder && image.blurhash) {
131
+ const { blurhashToImageCssString } = await import("@unpic/placeholder");
132
+ placeholderStyle = blurhashToImageCssString(image.blurhash);
133
+ } else if (placeholder && image.dominantColor) {
134
+ placeholderStyle = `background-color: ${image.dominantColor};`;
135
+ }
136
+
137
+ return {
138
+ key: image._key,
139
+ alt,
140
+ caption: image.caption,
141
+ width,
142
+ height,
143
+ src,
144
+ srcset,
145
+ sizes,
146
+ astroImageSrc,
147
+ placeholderStyle,
148
+ };
149
+ }),
150
+ );
38
151
  ---
39
152
 
40
153
  <div class="emdash-gallery" style={`--columns: ${columns}`}>
41
154
  {
42
- images.map((image) => {
43
- const src = buildRenderMediaUrl(Astro.locals.emdash?.getPublicMediaUrl, {
44
- url: image.asset.url,
45
- id: image.asset._ref,
46
- });
47
- const hasSize = image.width && image.height;
48
- return (
49
- <figure class="emdash-gallery-item">
50
- {hasSize ? (
51
- <AstroImage
52
- src={src}
53
- alt={image.alt || ""}
54
- width={image.width!}
55
- height={image.height!}
56
- layout="constrained"
57
- />
58
- ) : (
59
- <img src={src} alt={image.alt || ""} loading="lazy" decoding="async" />
60
- )}
61
- {image.caption && <figcaption>{image.caption}</figcaption>}
62
- </figure>
63
- );
64
- })
155
+ resolvedImages.map((image) => (
156
+ <figure class="emdash-gallery-item" style={image.placeholderStyle || undefined}>
157
+ {image.astroImageSrc ? (
158
+ <AstroImage
159
+ src={image.astroImageSrc}
160
+ alt={image.alt}
161
+ width={image.width!}
162
+ height={image.height!}
163
+ layout="constrained"
164
+ />
165
+ ) : (
166
+ <img
167
+ src={image.src}
168
+ srcset={image.srcset}
169
+ sizes={image.sizes}
170
+ alt={image.alt}
171
+ width={image.width}
172
+ height={image.height}
173
+ loading="lazy"
174
+ decoding="async"
175
+ />
176
+ )}
177
+ {image.caption && <figcaption>{image.caption}</figcaption>}
178
+ </figure>
179
+ ))
65
180
  }
66
181
  </div>
67
182
 
@@ -108,8 +108,9 @@ export interface ResolveSecretsOptions {
108
108
  db: Kysely<Database>;
109
109
  /**
110
110
  * Optional explicit env override map. When omitted, falls back to
111
- * `import.meta.env` via the global accessor below. Tests pass an
112
- * explicit map to avoid leaking process state.
111
+ * `process.env` via `readDefaultEnv` below (never `import.meta.env`
112
+ * see that function's docstring). Tests pass an explicit map to avoid
113
+ * leaking process state.
113
114
  */
114
115
  env?: SecretsEnv;
115
116
  /**
@@ -514,29 +515,34 @@ function decodeBase64urlStrict(input: string): Uint8Array | null {
514
515
  /**
515
516
  * Default env reader.
516
517
  *
517
- * Note: this is the **only** code path in core that reads both
518
- * `import.meta.env` and `process.env`. Route handlers should not — they
519
- * always run inside the Astro/Vite bundle where `import.meta.env` is
520
- * the correct source. This resolver is shared with the CLI surface (via
521
- * `cli/commands/secrets.ts`) which runs outside the bundle, so we
522
- * deliberately consult both. `import.meta.env` wins so build-time
523
- * substitutions are honored when present.
518
+ * Reads **only** `process.env`. This module must never reference
519
+ * `import.meta.env`: in an Astro/Vite production build the bare
520
+ * `import.meta.env` expression is statically replaced with an object
521
+ * literal built from the env loaded at build time, which (a) embeds any
522
+ * secret present in `.env` on the build machine into the shipped server
523
+ * bundle, and (b) makes that stale build-time value silently shadow the
524
+ * real runtime secret configured on the deployment platform (e.g. via
525
+ * `wrangler secret put`). See #2139.
526
+ *
527
+ * `process.env` is the runtime source on Node/container deployments and
528
+ * on Cloudflare Workers with Node compatibility (the same source
529
+ * `resolveS3Config` in `../storage/s3.ts` uses). The CLI surface
530
+ * (`cli/commands/secrets.ts`) runs outside the bundle where
531
+ * `process.env` is native.
524
532
  *
525
533
  * The convention documented in AGENTS.md ("import.meta.env.EMDASH_X ||
526
- * import.meta.env.X") is the route-handler convention; this is the
527
- * shared-with-CLI exception.
534
+ * import.meta.env.X") is for public, build-time config in route
535
+ * handlers; secrets are deliberately excluded from it.
528
536
  */
529
537
  function readDefaultEnv(): SecretsEnv {
530
- // eslint-disable-next-line typescript/no-unsafe-type-assertion -- import.meta.env is loose by design
531
- const meta = (import.meta.env ?? {}) as Record<string, string | undefined>;
532
538
  const proc = typeof process !== "undefined" && process.env ? process.env : {};
533
539
 
534
540
  return {
535
- EMDASH_ENCRYPTION_KEY: meta.EMDASH_ENCRYPTION_KEY ?? proc.EMDASH_ENCRYPTION_KEY,
536
- EMDASH_PREVIEW_SECRET: meta.EMDASH_PREVIEW_SECRET ?? proc.EMDASH_PREVIEW_SECRET,
537
- PREVIEW_SECRET: meta.PREVIEW_SECRET ?? proc.PREVIEW_SECRET,
538
- EMDASH_IP_SALT: meta.EMDASH_IP_SALT ?? proc.EMDASH_IP_SALT,
539
- EMDASH_AUTH_SECRET: meta.EMDASH_AUTH_SECRET ?? proc.EMDASH_AUTH_SECRET,
540
- AUTH_SECRET: meta.AUTH_SECRET ?? proc.AUTH_SECRET,
541
+ EMDASH_ENCRYPTION_KEY: proc.EMDASH_ENCRYPTION_KEY,
542
+ EMDASH_PREVIEW_SECRET: proc.EMDASH_PREVIEW_SECRET,
543
+ PREVIEW_SECRET: proc.PREVIEW_SECRET,
544
+ EMDASH_IP_SALT: proc.EMDASH_IP_SALT,
545
+ EMDASH_AUTH_SECRET: proc.EMDASH_AUTH_SECRET,
546
+ AUTH_SECRET: proc.AUTH_SECRET,
541
547
  };
542
548
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared sanitization for gallery block images, used by both converters so
3
+ * the editor round-trip and the stored shape stay in lockstep.
4
+ */
5
+
6
+ import type { PortableTextGalleryImage } from "./types.js";
7
+
8
+ /**
9
+ * Normalize an untrusted `images` value into well-formed gallery images.
10
+ * Non-object entries and entries without an asset object are dropped.
11
+ * Missing `_key`s are filled via `generateKey` when provided (PM → PT);
12
+ * left empty otherwise (PT → PM keeps whatever the block carried).
13
+ */
14
+ export function sanitizeGalleryImages(
15
+ value: unknown,
16
+ generateKey?: () => string,
17
+ ): PortableTextGalleryImage[] {
18
+ if (!Array.isArray(value)) return [];
19
+
20
+ const images: PortableTextGalleryImage[] = [];
21
+ for (const entry of value as unknown[]) {
22
+ if (!isRecord(entry)) continue;
23
+ const record = entry;
24
+ const asset = record.asset;
25
+ if (!isRecord(asset)) continue;
26
+ const assetRecord = asset;
27
+
28
+ const image: PortableTextGalleryImage = {
29
+ _type: "image",
30
+ _key:
31
+ typeof record._key === "string" && record._key
32
+ ? record._key
33
+ : generateKey
34
+ ? generateKey()
35
+ : "",
36
+ asset: {
37
+ _type: "reference",
38
+ _ref: typeof assetRecord._ref === "string" ? assetRecord._ref : "",
39
+ ...(typeof assetRecord.url === "string" && assetRecord.url ? { url: assetRecord.url } : {}),
40
+ ...(typeof assetRecord.provider === "string" && assetRecord.provider
41
+ ? { provider: assetRecord.provider }
42
+ : {}),
43
+ },
44
+ };
45
+ if (typeof record.alt === "string" && record.alt) image.alt = record.alt;
46
+ if (typeof record.caption === "string" && record.caption) image.caption = record.caption;
47
+ if (typeof record.width === "number") image.width = record.width;
48
+ if (typeof record.height === "number") image.height = record.height;
49
+ if (typeof record.blurhash === "string" && record.blurhash) image.blurhash = record.blurhash;
50
+ if (typeof record.dominantColor === "string" && record.dominantColor)
51
+ image.dominantColor = record.dominantColor;
52
+
53
+ images.push(image);
54
+ }
55
+
56
+ return images;
57
+ }
58
+
59
+ function isRecord(value: unknown): value is Record<string, unknown> {
60
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61
+ }
@@ -4,6 +4,7 @@
4
4
  * Converts Portable Text to TipTap's ProseMirror JSON format for editing.
5
5
  */
6
6
 
7
+ import { sanitizeGalleryImages } from "./gallery.js";
7
8
  import type {
8
9
  ProseMirrorDocument,
9
10
  ProseMirrorNode,
@@ -13,6 +14,7 @@ import type {
13
14
  PortableTextSpan,
14
15
  PortableTextMarkDef,
15
16
  PortableTextImageBlock,
17
+ PortableTextGalleryBlock,
16
18
  PortableTextCodeBlock,
17
19
  } from "./types.js";
18
20
 
@@ -128,6 +130,14 @@ function isImageBlock(block: PortableTextBlock): block is PortableTextImageBlock
128
130
  );
129
131
  }
130
132
 
133
+ /**
134
+ * Type guard for gallery blocks. Requires an `images` array — a gallery
135
+ * without one is malformed and falls through to the unknown-block path.
136
+ */
137
+ function isGalleryBlock(block: PortableTextBlock): block is PortableTextGalleryBlock {
138
+ return block._type === "gallery" && "images" in block && Array.isArray(block.images);
139
+ }
140
+
131
141
  /**
132
142
  * Type guard for code blocks
133
143
  */
@@ -149,6 +159,15 @@ function convertBlock(block: PortableTextBlock): ProseMirrorNode | null {
149
159
  // Malformed image block (no asset wrapper) — extract url from top level
150
160
  return convertMalformedImage(block);
151
161
  }
162
+ if (isGalleryBlock(block)) {
163
+ return {
164
+ type: "gallery",
165
+ attrs: {
166
+ images: sanitizeGalleryImages(block.images),
167
+ columns: typeof block.columns === "number" ? block.columns : undefined,
168
+ },
169
+ };
170
+ }
152
171
  if (isCodeBlock(block)) {
153
172
  return convertCodeBlock(block);
154
173
  }
@@ -4,6 +4,7 @@
4
4
  * Converts TipTap's ProseMirror JSON format to Portable Text for storage.
5
5
  */
6
6
 
7
+ import { sanitizeGalleryImages } from "./gallery.js";
7
8
  import type {
8
9
  ProseMirrorDocument,
9
10
  ProseMirrorNode,
@@ -13,6 +14,7 @@ import type {
13
14
  PortableTextSpan,
14
15
  PortableTextMarkDef,
15
16
  PortableTextImageBlock,
17
+ PortableTextGalleryBlock,
16
18
  PortableTextCodeBlock,
17
19
  PortableTextHtmlBlock,
18
20
  } from "./types.js";
@@ -77,6 +79,9 @@ function convertNode(node: ProseMirrorNode): PortableTextBlock | PortableTextBlo
77
79
  case "image":
78
80
  return convertImage(node);
79
81
 
82
+ case "gallery":
83
+ return convertGallery(node);
84
+
80
85
  case "horizontalRule":
81
86
  return {
82
87
  _type: "break",
@@ -327,6 +332,19 @@ function convertImage(node: ProseMirrorNode): PortableTextImageBlock {
327
332
  };
328
333
  }
329
334
 
335
+ /**
336
+ * Convert gallery node to Portable Text
337
+ */
338
+ function convertGallery(node: ProseMirrorNode): PortableTextGalleryBlock {
339
+ const columns = node.attrs?.columns;
340
+ return {
341
+ _type: "gallery",
342
+ _key: generateKey(),
343
+ images: sanitizeGalleryImages(node.attrs?.images, generateKey),
344
+ ...(typeof columns === "number" ? { columns } : {}),
345
+ };
346
+ }
347
+
330
348
  /**
331
349
  * Convert inline content (text nodes with marks) to Portable Text spans
332
350
  */
@@ -70,6 +70,41 @@ export interface PortableTextImageBlock {
70
70
  displayHeight?: number;
71
71
  }
72
72
 
73
+ /**
74
+ * A single image inside a gallery block. Mirrors the shape produced by
75
+ * gutenberg-to-portable-text and consumed by Gallery.astro.
76
+ */
77
+ export interface PortableTextGalleryImage {
78
+ _type: "image";
79
+ _key: string;
80
+ asset: {
81
+ /** Present on WordPress-imported galleries; always emitted on round-trip */
82
+ _type?: "reference";
83
+ _ref: string;
84
+ url?: string;
85
+ /** Provider ID for external media (e.g., "cloudflare-images") */
86
+ provider?: string;
87
+ };
88
+ alt?: string;
89
+ caption?: string;
90
+ width?: number;
91
+ height?: number;
92
+ /** LQIP blurhash placeholder (images only) */
93
+ blurhash?: string;
94
+ /** LQIP dominant-color placeholder, as a CSS color (images only) */
95
+ dominantColor?: string;
96
+ }
97
+
98
+ /**
99
+ * Gallery block (grid of images with optional per-image captions)
100
+ */
101
+ export interface PortableTextGalleryBlock {
102
+ _type: "gallery";
103
+ _key: string;
104
+ images: PortableTextGalleryImage[];
105
+ columns?: number;
106
+ }
107
+
73
108
  /**
74
109
  * Code block
75
110
  */
@@ -105,6 +140,7 @@ export interface PortableTextUnknownBlock {
105
140
  export type PortableTextBlock =
106
141
  | PortableTextTextBlock
107
142
  | PortableTextImageBlock
143
+ | PortableTextGalleryBlock
108
144
  | PortableTextCodeBlock
109
145
  | PortableTextHtmlBlock
110
146
  | PortableTextUnknownBlock;
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import type { ColumnDataType, Kysely, RawBuilder } from "kysely";
14
- import { sql } from "kysely";
14
+ import { PostgresAdapter, sql } from "kysely";
15
15
 
16
16
  import type { DatabaseDialectType } from "../db/adapters.js";
17
17
  import { validateIdentifier, validateJsonFieldName } from "./validate.js";
@@ -19,12 +19,11 @@ import { validateIdentifier, validateJsonFieldName } from "./validate.js";
19
19
  export type { DatabaseDialectType };
20
20
 
21
21
  /**
22
- * Detect dialect type from a Kysely instance via the adapter class name.
22
+ * Detect dialect type from a Kysely instance via the adapter class.
23
23
  */
24
24
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance
25
25
  export function detectDialect(db: Kysely<any>): DatabaseDialectType {
26
- const name = db.getExecutor().adapter.constructor.name;
27
- if (name === "PostgresAdapter") return "postgres";
26
+ if (db.getExecutor().adapter instanceof PostgresAdapter) return "postgres";
28
27
  return "sqlite";
29
28
  }
30
29
 
@@ -38,10 +38,7 @@ const KYSELY_PG_MIGRATION_LOCK_ID = BigInt("3853314791062309107");
38
38
 
39
39
  /**
40
40
  * Extends the stock adapter so every capability flag and `instanceof` check
41
- * is inherited, and keeps the class NAME `PostgresAdapter` because
42
- * `detectDialect()` (dialect-helpers.ts) identifies the dialect via
43
- * `adapter.constructor.name` — a differently-named adapter would make every
44
- * dialect helper fall back to SQLite SQL against a Postgres database.
41
+ * is inherited.
45
42
  */
46
43
  class PostgresAdapter extends KyselyPostgresAdapter {
47
44
  // eslint-disable-next-line typescript/no-explicit-any -- matches the DialectAdapter signature