emdash 0.27.0 → 0.28.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 (534) hide show
  1. package/dist/{adapters-u037EnTR.d.mts → adapters-hUy8vOB-.d.mts} +1 -1
  2. package/dist/{adapters-u037EnTR.d.mts.map → adapters-hUy8vOB-.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-D5FxMUo8.mjs → allowed-origins-CRsJ6BPM.mjs} +2 -2
  4. package/dist/{allowed-origins-D5FxMUo8.mjs.map → allowed-origins-CRsJ6BPM.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +17 -16
  7. package/dist/api/route-utils.mjs.map +1 -1
  8. package/dist/api/schemas/index.d.mts +1 -1
  9. package/dist/api/schemas/index.mjs +3 -3
  10. package/dist/{api-80kycR9o.mjs → api-BFURZn1i.mjs} +33 -23
  11. package/dist/api-BFURZn1i.mjs.map +1 -0
  12. package/dist/{api-tokens-D6ppjIVi.mjs → api-tokens-DvqeZmVq.mjs} +2 -2
  13. package/dist/{api-tokens-D6ppjIVi.mjs.map → api-tokens-DvqeZmVq.mjs.map} +1 -1
  14. package/dist/{apply-DSypk6Iu.mjs → apply-Bu-8nYHL.mjs} +111 -74
  15. package/dist/apply-Bu-8nYHL.mjs.map +1 -0
  16. package/dist/astro/index.d.mts +12 -12
  17. package/dist/astro/index.mjs +3 -3
  18. package/dist/astro/index.mjs.map +1 -1
  19. package/dist/astro/middleware/auth.d.mts +10 -10
  20. package/dist/astro/middleware/auth.d.mts.map +1 -1
  21. package/dist/astro/middleware/auth.mjs +50 -11
  22. package/dist/astro/middleware/auth.mjs.map +1 -1
  23. package/dist/astro/middleware/redirect.mjs +5 -5
  24. package/dist/astro/middleware/request-context.mjs +2 -2
  25. package/dist/astro/middleware/setup.mjs +1 -1
  26. package/dist/astro/middleware.d.mts +2 -2
  27. package/dist/astro/middleware.mjs +124 -71
  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 +14 -14
  38. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -14
  39. package/dist/astro/routes/api/admin/bylines/index.mjs +14 -14
  40. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +12 -12
  41. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  42. package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
  43. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  44. package/dist/astro/routes/api/admin/comments/index.mjs +10 -10
  45. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  46. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  47. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  48. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  49. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +37 -36
  50. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  51. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +37 -36
  52. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +36 -35
  54. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  55. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +36 -35
  56. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  57. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +36 -35
  58. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  59. package/dist/astro/routes/api/admin/plugins/index.mjs +36 -35
  60. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  61. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  62. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +36 -35
  63. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  64. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +36 -35
  65. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  66. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +36 -35
  67. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  68. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +36 -35
  69. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +37 -36
  71. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  72. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +36 -35
  73. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  74. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +37 -36
  75. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  76. package/dist/astro/routes/api/admin/plugins/updates.mjs +36 -35
  77. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  78. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +36 -35
  79. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  80. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  81. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +36 -35
  82. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  84. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  85. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  86. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  87. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  88. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  89. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  90. package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
  91. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  92. package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
  93. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  94. package/dist/astro/routes/api/auth/magic-link/send.mjs +9 -9
  95. package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
  96. package/dist/astro/routes/api/auth/me.mjs +7 -7
  97. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  98. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  99. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  100. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  101. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  102. package/dist/astro/routes/api/auth/passkey/options.mjs +11 -11
  103. package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
  104. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
  105. package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
  106. package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
  107. package/dist/astro/routes/api/auth/signup/request.mjs +9 -9
  108. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  109. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +13 -13
  110. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +12 -12
  111. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  112. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  113. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  114. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  115. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -10
  116. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +7 -7
  117. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  118. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  119. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +7 -7
  120. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
  121. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  122. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  123. package/dist/astro/routes/api/content/_collection_/_id_.mjs +7 -7
  124. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  125. package/dist/astro/routes/api/content/_collection_/index.mjs +7 -7
  126. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  127. package/dist/astro/routes/api/dashboard.mjs +8 -8
  128. package/dist/astro/routes/api/dev/emails.mjs +3 -3
  129. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  130. package/dist/astro/routes/api/import/probe.mjs +19 -11
  131. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  132. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  133. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -41
  134. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  135. package/dist/astro/routes/api/import/wordpress/execute.mjs +12 -407
  136. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  137. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  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.d.mts +19 -1
  141. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
  142. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +97 -79
  143. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  144. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  145. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +19 -11
  146. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  147. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +40 -2
  148. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
  149. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +519 -39
  150. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  151. package/dist/astro/routes/api/manifest.mjs +4 -4
  152. package/dist/astro/routes/api/mcp.mjs +39 -39
  153. package/dist/astro/routes/api/media/_id_/confirm.mjs +7 -7
  154. package/dist/astro/routes/api/media/_id_.mjs +7 -7
  155. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  156. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  157. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  158. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  159. package/dist/astro/routes/api/media/upload-url.mjs +8 -8
  160. package/dist/astro/routes/api/media.mjs +9 -9
  161. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +9 -9
  162. package/dist/astro/routes/api/menus/_name_/items.mjs +9 -9
  163. package/dist/astro/routes/api/menus/_name_/reorder.mjs +9 -9
  164. package/dist/astro/routes/api/menus/_name_/translations.mjs +9 -9
  165. package/dist/astro/routes/api/menus/_name_.mjs +9 -9
  166. package/dist/astro/routes/api/menus/index.mjs +9 -9
  167. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  168. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  169. package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
  170. package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
  171. package/dist/astro/routes/api/oauth/register.mjs +3 -3
  172. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  173. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  174. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  175. package/dist/astro/routes/api/openapi.json.mjs +4 -4
  176. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  177. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  178. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  179. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  180. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  181. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  182. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  183. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +36 -35
  184. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  185. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +36 -35
  186. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  187. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +36 -35
  188. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  189. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +36 -35
  190. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  191. package/dist/astro/routes/api/schema/collections/index.mjs +36 -35
  192. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  193. package/dist/astro/routes/api/schema/index.mjs +7 -6
  194. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  195. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +36 -35
  196. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  197. package/dist/astro/routes/api/schema/orphans/index.mjs +36 -35
  198. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  199. package/dist/astro/routes/api/search/enable.mjs +10 -10
  200. package/dist/astro/routes/api/search/index.mjs +9 -9
  201. package/dist/astro/routes/api/search/rebuild.mjs +10 -10
  202. package/dist/astro/routes/api/search/stats.mjs +6 -6
  203. package/dist/astro/routes/api/search/suggest.mjs +9 -9
  204. package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
  205. package/dist/astro/routes/api/sections/index.mjs +9 -9
  206. package/dist/astro/routes/api/settings/email.mjs +4 -4
  207. package/dist/astro/routes/api/settings.mjs +12 -12
  208. package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
  209. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  210. package/dist/astro/routes/api/setup/dev-bypass.mjs +25 -24
  211. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  212. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  213. package/dist/astro/routes/api/setup/index.mjs +26 -25
  214. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  215. package/dist/astro/routes/api/setup/status.mjs +4 -4
  216. package/dist/astro/routes/api/snapshot.mjs +6 -6
  217. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
  218. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
  219. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
  220. package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
  221. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  222. package/dist/astro/routes/api/typegen.mjs +5 -5
  223. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  224. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  225. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  226. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  227. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -9
  228. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -9
  229. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  230. package/dist/astro/routes/api/widget-areas/index.mjs +9 -9
  231. package/dist/astro/routes/api/widget-components.mjs +3 -3
  232. package/dist/astro/routes/robots.txt.mjs +6 -6
  233. package/dist/astro/routes/sitemap-_collection_.xml.mjs +9 -9
  234. package/dist/astro/routes/sitemap.xml.mjs +7 -7
  235. package/dist/astro/types.d.mts +13 -13
  236. package/dist/auth/providers/github.d.mts +1 -1
  237. package/dist/auth/providers/google.d.mts +1 -1
  238. package/dist/{authorize-C3a_zGsM.mjs → authorize-Dz88F_Fw.mjs} +2 -2
  239. package/dist/{authorize-C3a_zGsM.mjs.map → authorize-Dz88F_Fw.mjs.map} +1 -1
  240. package/dist/{byline-DEKSVPTy.mjs → byline-Bm8ZgUP9.mjs} +6 -6
  241. package/dist/{byline-DEKSVPTy.mjs.map → byline-Bm8ZgUP9.mjs.map} +1 -1
  242. package/dist/{byline-fields-eyJMblSr.mjs → byline-fields-2Lp5Hbgf.mjs} +18 -3
  243. package/dist/byline-fields-2Lp5Hbgf.mjs.map +1 -0
  244. package/dist/{byline-fields-DaMKzkhO.mjs → byline-fields-CIx_GuN4.mjs} +1 -1
  245. package/dist/{byline-fields-DaMKzkhO.mjs.map → byline-fields-CIx_GuN4.mjs.map} +1 -1
  246. package/dist/{byline-fields-CtQ6blXd.d.mts → byline-fields-Cgrt9-aL.d.mts} +49 -34
  247. package/dist/{byline-fields-CtQ6blXd.d.mts.map → byline-fields-Cgrt9-aL.d.mts.map} +1 -1
  248. package/dist/{bylines-Ds4-otXl.mjs → bylines-6tm9mgQb.mjs} +4 -4
  249. package/dist/{bylines-Ds4-otXl.mjs.map → bylines-6tm9mgQb.mjs.map} +1 -1
  250. package/dist/{bylines-CiqgTHWF.mjs → bylines-Woq87129.mjs} +6 -6
  251. package/dist/{bylines-CiqgTHWF.mjs.map → bylines-Woq87129.mjs.map} +1 -1
  252. package/dist/{cache-Ciu3e3AF.mjs → cache-CEBAZjir.mjs} +2 -2
  253. package/dist/{cache-Ciu3e3AF.mjs.map → cache-CEBAZjir.mjs.map} +1 -1
  254. package/dist/{challenge-store-LhiqMccz.mjs → challenge-store-anhwVER0.mjs} +1 -1
  255. package/dist/{challenge-store-LhiqMccz.mjs.map → challenge-store-anhwVER0.mjs.map} +1 -1
  256. package/dist/{chunks-CZFiauig.mjs → chunks-B2isWQhr.mjs} +2 -2
  257. package/dist/{chunks-CZFiauig.mjs.map → chunks-B2isWQhr.mjs.map} +1 -1
  258. package/dist/cli/index.mjs +22 -21
  259. package/dist/cli/index.mjs.map +1 -1
  260. package/dist/client/cf-access.d.mts +1 -1
  261. package/dist/client/index.d.mts +1 -1
  262. package/dist/client/index.mjs +2 -2
  263. package/dist/{comment-CH1IrFFs.mjs → comment-CzeatwBV.mjs} +3 -3
  264. package/dist/{comment-CH1IrFFs.mjs.map → comment-CzeatwBV.mjs.map} +1 -1
  265. package/dist/{comment-reaction-kruNY-kP.mjs → comment-reaction-Cj9QJY7I.mjs} +2 -2
  266. package/dist/{comment-reaction-kruNY-kP.mjs.map → comment-reaction-Cj9QJY7I.mjs.map} +1 -1
  267. package/dist/{comments-BUJyXbgq.mjs → comments-BLE-jv5k.mjs} +3 -3
  268. package/dist/{comments-BUJyXbgq.mjs.map → comments-BLE-jv5k.mjs.map} +1 -1
  269. package/dist/{components-CYt4uVK9.mjs → components-B_sATNUc.mjs} +1 -1
  270. package/dist/{components-CYt4uVK9.mjs.map → components-B_sATNUc.mjs.map} +1 -1
  271. package/dist/{content-RmfHoWqK.mjs → content-DhgEth4k.mjs} +4 -4
  272. package/dist/{content-RmfHoWqK.mjs.map → content-DhgEth4k.mjs.map} +1 -1
  273. package/dist/content-refresh-CA4uKwzH.mjs +1531 -0
  274. package/dist/content-refresh-CA4uKwzH.mjs.map +1 -0
  275. package/dist/{context-CzUU_WzL.mjs → context-CUsR9dXL.mjs} +76 -54
  276. package/dist/context-CUsR9dXL.mjs.map +1 -0
  277. package/dist/{cron-C5LVoNmP.mjs → cron-v4FTEFR_.mjs} +1 -1
  278. package/dist/{cron-C5LVoNmP.mjs.map → cron-v4FTEFR_.mjs.map} +1 -1
  279. package/dist/{dashboard-CVyGhift.mjs → dashboard-EvEKF5en.mjs} +4 -4
  280. package/dist/{dashboard-CVyGhift.mjs.map → dashboard-EvEKF5en.mjs.map} +1 -1
  281. package/dist/db/index.d.mts +3 -3
  282. package/dist/db/index.mjs +1 -1
  283. package/dist/db/libsql.d.mts +1 -1
  284. package/dist/db/postgres.d.mts +1 -1
  285. package/dist/db/sqlite.d.mts +1 -1
  286. package/dist/{default-CeRG-Ot4.mjs → default-CM_D83Mg.mjs} +1 -1
  287. package/dist/{default-CeRG-Ot4.mjs.map → default-CM_D83Mg.mjs.map} +1 -1
  288. package/dist/{device-flow-DENDCQ9F.mjs → device-flow-CfGP18Uw.mjs} +4 -4
  289. package/dist/{device-flow-DENDCQ9F.mjs.map → device-flow-CfGP18Uw.mjs.map} +1 -1
  290. package/dist/{email-console-DJP32ucW.mjs → email-console-CIf4Lfvv.mjs} +1 -1
  291. package/dist/{email-console-DJP32ucW.mjs.map → email-console-CIf4Lfvv.mjs.map} +1 -1
  292. package/dist/{error-BHkLZk-w.mjs → error-CkAseSqZ.mjs} +2 -2
  293. package/dist/{error-BHkLZk-w.mjs.map → error-CkAseSqZ.mjs.map} +1 -1
  294. package/dist/{escape-Bjio4ZsM.mjs → escape-DqD0aCKu.mjs} +1 -1
  295. package/dist/{escape-Bjio4ZsM.mjs.map → escape-DqD0aCKu.mjs.map} +1 -1
  296. package/dist/{field-defs-cache-BA-8ZPiT.mjs → field-defs-cache-WUI88LX5.mjs} +2 -2
  297. package/dist/{field-defs-cache-BA-8ZPiT.mjs.map → field-defs-cache-WUI88LX5.mjs.map} +1 -1
  298. package/dist/{fts-manager-Db9jGavf.mjs → fts-manager-BbFaF_V8.mjs} +2 -2
  299. package/dist/{fts-manager-Db9jGavf.mjs.map → fts-manager-BbFaF_V8.mjs.map} +1 -1
  300. package/dist/{import-Bb1T9WJS.mjs → import-fYLf14Co.mjs} +514 -25
  301. package/dist/import-fYLf14Co.mjs.map +1 -0
  302. package/dist/{index-CDZZjrLT.d.mts → index-B9sIxwe4.d.mts} +21 -22
  303. package/dist/{index-CDZZjrLT.d.mts.map → index-B9sIxwe4.d.mts.map} +1 -1
  304. package/dist/{index-DGIjmUXQ.d.mts → index-Bm-Gy8Mh.d.mts} +25 -4
  305. package/dist/index-Bm-Gy8Mh.d.mts.map +1 -0
  306. package/dist/index.d.mts +18 -18
  307. package/dist/index.mjs +54 -53
  308. package/dist/{load-DpHICVPh.mjs → load-Djn3HWQS.mjs} +2 -2
  309. package/dist/{load-DpHICVPh.mjs.map → load-Djn3HWQS.mjs.map} +1 -1
  310. package/dist/{loader-4K37qA-y.mjs → loader-CB6YHeul.mjs} +6 -5
  311. package/dist/loader-CB6YHeul.mjs.map +1 -0
  312. package/dist/{manifest-schema-kPGX7VS-.mjs → manifest-schema-BHlgZM1W.mjs} +1 -1
  313. package/dist/{manifest-schema-kPGX7VS-.mjs.map → manifest-schema-BHlgZM1W.mjs.map} +1 -1
  314. package/dist/media/index.d.mts +1 -1
  315. package/dist/media/local-runtime.d.mts +12 -12
  316. package/dist/media/local-runtime.mjs +5 -5
  317. package/dist/{media-C0nRRfs1.mjs → media-BrXIaMFN.mjs} +2 -2
  318. package/dist/{media-C0nRRfs1.mjs.map → media-BrXIaMFN.mjs.map} +1 -1
  319. package/dist/{media-allowlist-Du8t1u6a.mjs → media-allowlist-CRHEeJ_i.mjs} +1 -1
  320. package/dist/{media-allowlist-Du8t1u6a.mjs.map → media-allowlist-CRHEeJ_i.mjs.map} +1 -1
  321. package/dist/{media-url-O4rm9-SQ.mjs → media-url-CKi5XSw4.mjs} +1 -1
  322. package/dist/{media-url-O4rm9-SQ.mjs.map → media-url-CKi5XSw4.mjs.map} +1 -1
  323. package/dist/{menus-SL2K0xq2.mjs → menus-Cfivc2Ad.mjs} +3 -3
  324. package/dist/{menus-SL2K0xq2.mjs.map → menus-Cfivc2Ad.mjs.map} +1 -1
  325. package/dist/{menus-DzRyC1ID.mjs → menus-CsZ2ZEK2.mjs} +16 -12
  326. package/dist/{menus-DzRyC1ID.mjs.map → menus-CsZ2ZEK2.mjs.map} +1 -1
  327. package/dist/{mode-BB0F8xTC.mjs → mode-BM9QgEmt.mjs} +1 -1
  328. package/dist/{mode-BB0F8xTC.mjs.map → mode-BM9QgEmt.mjs.map} +1 -1
  329. package/dist/{oauth-authorization-CsvzIp_F.mjs → oauth-authorization-CXWkIGT4.mjs} +4 -4
  330. package/dist/{oauth-authorization-CsvzIp_F.mjs.map → oauth-authorization-CXWkIGT4.mjs.map} +1 -1
  331. package/dist/{oauth-clients-C9SYwEbZ.mjs → oauth-clients-BmTI5rsl.mjs} +1 -1
  332. package/dist/{oauth-clients-C9SYwEbZ.mjs.map → oauth-clients-BmTI5rsl.mjs.map} +1 -1
  333. package/dist/{oauth-state-store---zrApfB.mjs → oauth-state-store-YLFLcH5F.mjs} +1 -1
  334. package/dist/{oauth-state-store---zrApfB.mjs.map → oauth-state-store-YLFLcH5F.mjs.map} +1 -1
  335. package/dist/{oauth-user-lookup-SHsWRlG9.mjs → oauth-user-lookup-Bsc_GsX-.mjs} +1 -1
  336. package/dist/{oauth-user-lookup-SHsWRlG9.mjs.map → oauth-user-lookup-Bsc_GsX-.mjs.map} +1 -1
  337. package/dist/object-cache/memory.d.mts +1 -1
  338. package/dist/{object-cache-CHbHv83-.mjs → object-cache-Bok5j2ae.mjs} +43 -3
  339. package/dist/object-cache-Bok5j2ae.mjs.map +1 -0
  340. package/dist/{options-41nCWqi9.d.mts → options-iMpRRxIe.d.mts} +3 -3
  341. package/dist/{options-41nCWqi9.d.mts.map → options-iMpRRxIe.d.mts.map} +1 -1
  342. package/dist/page/index.d.mts +2 -2
  343. package/dist/{parse-CqyZj3Hp.mjs → parse-GmjecGct.mjs} +2 -2
  344. package/dist/{parse-CqyZj3Hp.mjs.map → parse-GmjecGct.mjs.map} +1 -1
  345. package/dist/{passkey-config-C0YfSBko.mjs → passkey-config-De0ahnRg.mjs} +1 -1
  346. package/dist/{passkey-config-C0YfSBko.mjs.map → passkey-config-De0ahnRg.mjs.map} +1 -1
  347. package/dist/{placeholder-Cuce9U-m.d.mts → placeholder-BSKwzRe4.d.mts} +1 -1
  348. package/dist/{placeholder-Cuce9U-m.d.mts.map → placeholder-BSKwzRe4.d.mts.map} +1 -1
  349. package/dist/plugin-types.d.mts +1 -1
  350. package/dist/plugin-utils.d.mts +10 -10
  351. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  352. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  353. package/dist/{portable-text-BICg8bTk.mjs → portable-text-cCa0cGV6.mjs} +1 -1
  354. package/dist/{portable-text-BICg8bTk.mjs.map → portable-text-cCa0cGV6.mjs.map} +1 -1
  355. package/dist/{preview-DKGCt2_p.mjs → preview-SSXNVPAC.mjs} +2 -2
  356. package/dist/{preview-DKGCt2_p.mjs.map → preview-SSXNVPAC.mjs.map} +1 -1
  357. package/dist/{public-url-CTVqgMmg.mjs → public-url-Danv47cS.mjs} +1 -1
  358. package/dist/{public-url-CTVqgMmg.mjs.map → public-url-Danv47cS.mjs.map} +1 -1
  359. package/dist/{query-B0M-9LYR.mjs → query-ByGq6ver.mjs} +14 -14
  360. package/dist/{query-B0M-9LYR.mjs.map → query-ByGq6ver.mjs.map} +1 -1
  361. package/dist/{rate-limit-BwoUdIpC.mjs → rate-limit-C_Lf63Dd.mjs} +2 -2
  362. package/dist/{rate-limit-BwoUdIpC.mjs.map → rate-limit-C_Lf63Dd.mjs.map} +1 -1
  363. package/dist/{redirect-CS-PHtNh.mjs → redirect-CoW3G-0U.mjs} +1 -1
  364. package/dist/{redirect-CS-PHtNh.mjs.map → redirect-CoW3G-0U.mjs.map} +1 -1
  365. package/dist/{redirect-lMOzG04S.mjs → redirect-IyfNirgS.mjs} +2 -2
  366. package/dist/{redirect-lMOzG04S.mjs.map → redirect-IyfNirgS.mjs.map} +1 -1
  367. package/dist/{redirects-9218MmYl.mjs → redirects-Bs1g42Ri.mjs} +4 -4
  368. package/dist/{redirects-9218MmYl.mjs.map → redirects-Bs1g42Ri.mjs.map} +1 -1
  369. package/dist/{redirects-Dcbuisoj.mjs → redirects-DXEXBAII.mjs} +2 -2
  370. package/dist/{redirects-Dcbuisoj.mjs.map → redirects-DXEXBAII.mjs.map} +1 -1
  371. package/dist/{registry-DP-iEtdR.mjs → registry-B65mUtPa.mjs} +122 -79
  372. package/dist/registry-B65mUtPa.mjs.map +1 -0
  373. package/dist/{request-meta-CmS1tDFf.mjs → request-meta-DbvXaAvU.mjs} +2 -2
  374. package/dist/{request-meta-CmS1tDFf.mjs.map → request-meta-DbvXaAvU.mjs.map} +1 -1
  375. package/dist/{resolve-C7I0qiR0.mjs → resolve-DR3wqMTy.mjs} +1 -1
  376. package/dist/{resolve-C7I0qiR0.mjs.map → resolve-DR3wqMTy.mjs.map} +1 -1
  377. package/dist/{runner-BbR3DfrL.d.mts → runner-DvFWoz8-.d.mts} +2 -2
  378. package/dist/{runner-BbR3DfrL.d.mts.map → runner-DvFWoz8-.d.mts.map} +1 -1
  379. package/dist/{runner-CFngB4WZ.mjs → runner-K8XK101B.mjs} +309 -190
  380. package/dist/runner-K8XK101B.mjs.map +1 -0
  381. package/dist/runtime.d.mts +11 -11
  382. package/dist/runtime.mjs +2 -2
  383. package/dist/{scheduled-publish-BMljtY5G.d.mts → scheduled-publish-Drffrf2a.d.mts} +1 -1
  384. package/dist/scheduled-publish-Drffrf2a.d.mts.map +1 -0
  385. package/dist/{schema-BfJ7bIXH.mjs → schema-Blhoxp3P.mjs} +5 -5
  386. package/dist/{schema-BfJ7bIXH.mjs.map → schema-Blhoxp3P.mjs.map} +1 -1
  387. package/dist/{search-C7t3PH1J.mjs → search-DZN2HZ-V.mjs} +4 -4
  388. package/dist/{search-C7t3PH1J.mjs.map → search-DZN2HZ-V.mjs.map} +1 -1
  389. package/dist/{secrets-BSf9pRRY.mjs → secrets-kp760LlD.mjs} +2 -2
  390. package/dist/{secrets-BSf9pRRY.mjs.map → secrets-kp760LlD.mjs.map} +1 -1
  391. package/dist/{sections-Dm0CVX23.mjs → sections-CpF5SaGr.mjs} +3 -3
  392. package/dist/{sections-Dm0CVX23.mjs.map → sections-CpF5SaGr.mjs.map} +1 -1
  393. package/dist/seed/index.d.mts +2 -2
  394. package/dist/seed/index.mjs +19 -18
  395. package/dist/seo/index.d.mts +1 -1
  396. package/dist/seo/index.mjs +1 -1
  397. package/dist/{seo-BkhuuaaE.mjs → seo-89-53DDs.mjs} +1 -1
  398. package/dist/{seo-BkhuuaaE.mjs.map → seo-89-53DDs.mjs.map} +1 -1
  399. package/dist/{seo-Dd3tbY_k.mjs → seo-DXu2UQnC.mjs} +3 -3
  400. package/dist/{seo-Dd3tbY_k.mjs.map → seo-DXu2UQnC.mjs.map} +1 -1
  401. package/dist/{service-CTrqp_Jy.mjs → service-DQmiBMzt.mjs} +3 -3
  402. package/dist/{service-CTrqp_Jy.mjs.map → service-DQmiBMzt.mjs.map} +1 -1
  403. package/dist/{session-user-B8aLtKAH.mjs → session-user-Cascbax7.mjs} +1 -1
  404. package/dist/{session-user-B8aLtKAH.mjs.map → session-user-Cascbax7.mjs.map} +1 -1
  405. package/dist/{settings-CZ7ZP9cW.mjs → settings-rZyHEcNT.mjs} +3 -3
  406. package/dist/{settings-CZ7ZP9cW.mjs.map → settings-rZyHEcNT.mjs.map} +1 -1
  407. package/dist/{settings-BX8cWBus.mjs → settings-zhmdKyJQ.mjs} +5 -5
  408. package/dist/{settings-BX8cWBus.mjs.map → settings-zhmdKyJQ.mjs.map} +1 -1
  409. package/dist/{setup-complete-gEiySUc-.mjs → setup-complete-Cb3LRyzy.mjs} +1 -1
  410. package/dist/{setup-complete-gEiySUc-.mjs.map → setup-complete-Cb3LRyzy.mjs.map} +1 -1
  411. package/dist/{setup-nonce-DzS50zme.mjs → setup-nonce-Z8x0tHf8.mjs} +1 -1
  412. package/dist/{setup-nonce-DzS50zme.mjs.map → setup-nonce-Z8x0tHf8.mjs.map} +1 -1
  413. package/dist/{single-flight-cache-Cdfkic3t.mjs → single-flight-cache-BG3B_GhN.mjs} +1 -1
  414. package/dist/{single-flight-cache-Cdfkic3t.mjs.map → single-flight-cache-BG3B_GhN.mjs.map} +1 -1
  415. package/dist/{site-url-BLebyON8.mjs → site-url-BBfDTgZB.mjs} +1 -1
  416. package/dist/{site-url-BLebyON8.mjs.map → site-url-BBfDTgZB.mjs.map} +1 -1
  417. package/dist/{ssrf-BRKb343l.mjs → ssrf-1g7usGBg.mjs} +1 -1
  418. package/dist/{ssrf-BRKb343l.mjs.map → ssrf-1g7usGBg.mjs.map} +1 -1
  419. package/dist/{status-vUK0SA17.mjs → status-CqJQX1QN.mjs} +1 -1
  420. package/dist/{status-vUK0SA17.mjs.map → status-CqJQX1QN.mjs.map} +1 -1
  421. package/dist/storage/local.d.mts +1 -1
  422. package/dist/storage/local.mjs +1 -1
  423. package/dist/storage/s3.d.mts +1 -1
  424. package/dist/storage/s3.mjs +1 -1
  425. package/dist/{taxonomies-DChKcVxf.mjs → taxonomies-BGBd16Eu.mjs} +4 -4
  426. package/dist/{taxonomies-DChKcVxf.mjs.map → taxonomies-BGBd16Eu.mjs.map} +1 -1
  427. package/dist/{taxonomies-ckPjz-6d.mjs → taxonomies-Cnb4F-Pb.mjs} +7 -7
  428. package/dist/{taxonomies-ckPjz-6d.mjs.map → taxonomies-Cnb4F-Pb.mjs.map} +1 -1
  429. package/dist/{taxonomy-Cru-he6s.mjs → taxonomy-BxGxZZdu.mjs} +4 -4
  430. package/dist/{taxonomy-Cru-he6s.mjs.map → taxonomy-BxGxZZdu.mjs.map} +1 -1
  431. package/dist/{tokens-LeuXF9gG.mjs → tokens-BGT0TUUo.mjs} +1 -1
  432. package/dist/{tokens-LeuXF9gG.mjs.map → tokens-BGT0TUUo.mjs.map} +1 -1
  433. package/dist/{transport-Blrl2k_o.d.mts → transport-BMDWHSgZ.d.mts} +1 -1
  434. package/dist/{transport-Blrl2k_o.d.mts.map → transport-BMDWHSgZ.d.mts.map} +1 -1
  435. package/dist/{transport-_2nBz7e9.mjs → transport-DPefPMcz.mjs} +1 -1
  436. package/dist/{transport-_2nBz7e9.mjs.map → transport-DPefPMcz.mjs.map} +1 -1
  437. package/dist/{trusted-proxy-DZY5WCn2.mjs → trusted-proxy-VTPRiX2s.mjs} +1 -1
  438. package/dist/{trusted-proxy-DZY5WCn2.mjs.map → trusted-proxy-VTPRiX2s.mjs.map} +1 -1
  439. package/dist/{types-ETmO_jQr.d.mts → types-BcLUUbbR.d.mts} +2 -2
  440. package/dist/{types-ETmO_jQr.d.mts.map → types-BcLUUbbR.d.mts.map} +1 -1
  441. package/dist/{types-BYnDBVwe.d.mts → types-CGbYowGQ.d.mts} +2 -2
  442. package/dist/{types-BYnDBVwe.d.mts.map → types-CGbYowGQ.d.mts.map} +1 -1
  443. package/dist/{types-CvuKO5Pn.d.mts → types-CgvhU-hF.d.mts} +1 -1
  444. package/dist/{types-CvuKO5Pn.d.mts.map → types-CgvhU-hF.d.mts.map} +1 -1
  445. package/dist/{types-Y09-wtyU.d.mts → types-ClUkdbWb.d.mts} +1 -1
  446. package/dist/{types-Y09-wtyU.d.mts.map → types-ClUkdbWb.d.mts.map} +1 -1
  447. package/dist/{types-DU4TSFS5.mjs → types-CuW0zWDA.mjs} +2 -2
  448. package/dist/{types-DU4TSFS5.mjs.map → types-CuW0zWDA.mjs.map} +1 -1
  449. package/dist/{types-BkZ8DUEI.d.mts → types-DmHk7vAt.d.mts} +1 -1
  450. package/dist/{types-BkZ8DUEI.d.mts.map → types-DmHk7vAt.d.mts.map} +1 -1
  451. package/dist/{types-CQAugunJ.mjs → types-Jo3UYd6b.mjs} +1 -1
  452. package/dist/{types-CQAugunJ.mjs.map → types-Jo3UYd6b.mjs.map} +1 -1
  453. package/dist/{types-xIfVRNLp.d.mts → types-_HFLRzTn.d.mts} +22 -1
  454. package/dist/types-_HFLRzTn.d.mts.map +1 -0
  455. package/dist/{types-ByChcBgE.d.mts → types-ea9sAPck.d.mts} +1 -1
  456. package/dist/{types-ByChcBgE.d.mts.map → types-ea9sAPck.d.mts.map} +1 -1
  457. package/dist/{types-CNlaBFzx.d.mts → types-g_aFIdW0.d.mts} +1 -1
  458. package/dist/{types-CNlaBFzx.d.mts.map → types-g_aFIdW0.d.mts.map} +1 -1
  459. package/dist/{types-Dbqff978.d.mts → types-vgEWF18A.d.mts} +19 -1
  460. package/dist/{types-Dbqff978.d.mts.map → types-vgEWF18A.d.mts.map} +1 -1
  461. package/dist/{user-DgGbKL9s.mjs → user-BjAOvU3Z.mjs} +3 -3
  462. package/dist/{user-DgGbKL9s.mjs.map → user-BjAOvU3Z.mjs.map} +1 -1
  463. package/dist/{utils-B7A57fm9.mjs → utils-BJXElxdw.mjs} +156 -7
  464. package/dist/utils-BJXElxdw.mjs.map +1 -0
  465. package/dist/{validate-CRkcYZAR.mjs → validate-CWSnKVIJ.mjs} +2 -2
  466. package/dist/{validate-CRkcYZAR.mjs.map → validate-CWSnKVIJ.mjs.map} +1 -1
  467. package/dist/{validate-9ECmtEpJ.d.mts → validate-Z1b86_3B.d.mts} +5 -5
  468. package/dist/{validate-9ECmtEpJ.d.mts.map → validate-Z1b86_3B.d.mts.map} +1 -1
  469. package/dist/{validation-sR26SOwD.mjs → validation-c8WjhH8z.mjs} +8 -5
  470. package/dist/{validation-sR26SOwD.mjs.map → validation-c8WjhH8z.mjs.map} +1 -1
  471. package/dist/version-CaccU7DM.mjs +7 -0
  472. package/dist/{version-CrqhKGWn.mjs.map → version-CaccU7DM.mjs.map} +1 -1
  473. package/dist/{widgets-DRgtn7Cl.mjs → widgets-hWcIicX8.mjs} +3 -3
  474. package/dist/{widgets-DRgtn7Cl.mjs.map → widgets-hWcIicX8.mjs.map} +1 -1
  475. package/dist/wxr-taxonomies-0_uxSrmf.d.mts +34 -0
  476. package/dist/wxr-taxonomies-0_uxSrmf.d.mts.map +1 -0
  477. package/dist/wxr-taxonomies-BC3OYpvx.mjs +425 -0
  478. package/dist/wxr-taxonomies-BC3OYpvx.mjs.map +1 -0
  479. package/dist/{zod-generator-C5JaV--i.mjs → zod-generator-CacrR-i8.mjs} +2 -2
  480. package/dist/{zod-generator-C5JaV--i.mjs.map → zod-generator-CacrR-i8.mjs.map} +1 -1
  481. package/package.json +7 -7
  482. package/src/api/handlers/content.ts +10 -7
  483. package/src/api/schemas/import.ts +19 -0
  484. package/src/astro/integration/runtime.ts +3 -6
  485. package/src/astro/middleware/auth.ts +9 -3
  486. package/src/astro/middleware/csp.ts +54 -3
  487. package/src/astro/routes/admin.astro +9 -0
  488. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +122 -98
  489. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +794 -44
  490. package/src/components/EmDashImage.astro +59 -23
  491. package/src/components/Image.astro +36 -35
  492. package/src/database/migrations/049_taxonomies_name_locale_index.ts +42 -0
  493. package/src/database/migrations/050_media_usage_index_status.ts +142 -0
  494. package/src/database/migrations/runner.ts +4 -0
  495. package/src/database/repositories/media-usage.ts +932 -58
  496. package/src/database/types.ts +22 -0
  497. package/src/emdash-runtime.ts +123 -3
  498. package/src/import/comments.ts +159 -0
  499. package/src/import/index.ts +8 -1
  500. package/src/import/settings.ts +65 -189
  501. package/src/import/sources/wordpress-plugin.ts +338 -42
  502. package/src/import/sources/wxr.ts +4 -1
  503. package/src/import/types.ts +12 -0
  504. package/src/import/utils.ts +207 -0
  505. package/src/import/wxr-taxonomies.ts +39 -0
  506. package/src/loader.ts +13 -2
  507. package/src/media/usage/content-fields.ts +119 -0
  508. package/src/media/usage/content-refresh.ts +524 -0
  509. package/src/media/usage/content-repair.ts +491 -0
  510. package/src/media/usage/content-snapshots.ts +435 -0
  511. package/src/media/usage/extractor.ts +4 -4
  512. package/src/media/usage/source-key.ts +22 -0
  513. package/src/menus/index.ts +17 -1
  514. package/src/object-cache/index.ts +82 -7
  515. package/src/plugins/context.ts +100 -71
  516. package/src/schema/registry.ts +240 -154
  517. package/src/seed/apply.ts +156 -99
  518. package/dist/api-80kycR9o.mjs.map +0 -1
  519. package/dist/apply-DSypk6Iu.mjs.map +0 -1
  520. package/dist/byline-fields-eyJMblSr.mjs.map +0 -1
  521. package/dist/context-CzUU_WzL.mjs.map +0 -1
  522. package/dist/import-Bb1T9WJS.mjs.map +0 -1
  523. package/dist/index-DGIjmUXQ.d.mts.map +0 -1
  524. package/dist/loader-4K37qA-y.mjs.map +0 -1
  525. package/dist/object-cache-CHbHv83-.mjs.map +0 -1
  526. package/dist/registry-DP-iEtdR.mjs.map +0 -1
  527. package/dist/runner-CFngB4WZ.mjs.map +0 -1
  528. package/dist/scheduled-publish-BMljtY5G.d.mts.map +0 -1
  529. package/dist/types-xIfVRNLp.d.mts.map +0 -1
  530. package/dist/utils-B7A57fm9.mjs.map +0 -1
  531. package/dist/version-CrqhKGWn.mjs +0 -7
  532. /package/dist/{api-tokens-B4BQybOp.mjs → api-tokens-DsvvCtdh.mjs} +0 -0
  533. /package/dist/{ssrf-CcX9zvMK.mjs → ssrf-CmuGgCq3.mjs} +0 -0
  534. /package/dist/{types-BvbeGEtr.mjs → types-DROWRxYV.mjs} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"zod-generator-C5JaV--i.mjs","names":[],"sources":["../src/schema/zod-generator.ts"],"sourcesContent":["import { z, type ZodTypeAny } from \"zod\";\n\nimport { hashString } from \"../utils/hash.js\";\nimport type { Field, FieldType, CollectionWithFields } from \"./types.js\";\n\n/** Pattern to split on underscores, hyphens, and spaces for PascalCase conversion */\nconst PASCAL_CASE_SPLIT_PATTERN = /[_\\-\\s]+/;\n\n/**\n * Generate a Zod schema from a collection's field definitions\n *\n * This allows runtime validation of content based on dynamically\n * defined schemas stored in D1.\n */\nexport function generateZodSchema(\n\tcollection: CollectionWithFields,\n): z.ZodObject<Record<string, ZodTypeAny>> {\n\tconst shape: Record<string, ZodTypeAny> = {};\n\n\tfor (const field of collection.fields) {\n\t\tshape[field.slug] = generateFieldSchema(field);\n\t}\n\n\treturn z.object(shape);\n}\n\n/**\n * Generate Zod schema for a single field\n */\nexport function generateFieldSchema(field: Field): ZodTypeAny {\n\tlet schema = getBaseSchema(field.type, field);\n\n\t// Apply validation rules\n\tif (field.validation) {\n\t\tschema = applyValidation(schema, field);\n\t}\n\n\t// Apply required/optional. Non-required fields use `.nullish()` rather\n\t// than `.optional()` because the underlying SQLite columns are nullable\n\t// (see `SchemaRegistry.addFieldColumn` -- non-required fields are added\n\t// without `NOT NULL`). The admin re-sends what it loaded from the\n\t// server on autosave, so any field that's actually `null` in the DB\n\t// must round-trip cleanly through the validator. `.optional()` only\n\t// accepts `undefined`; `.nullish()` accepts both `undefined` and\n\t// `null`. (#867 — autosave failures on seeded entries.)\n\tif (!field.required) {\n\t\tschema = schema.nullish();\n\t}\n\n\t// Apply default value\n\tif (field.defaultValue !== undefined) {\n\t\tschema = schema.default(field.defaultValue);\n\t}\n\n\treturn schema;\n}\n\n/**\n * Get base Zod schema for a field type\n */\nfunction getBaseSchema(type: FieldType, field: Field): ZodTypeAny {\n\tswitch (type) {\n\t\tcase \"url\":\n\t\t\treturn z.string().url();\n\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\t\treturn z.string();\n\n\t\tcase \"number\":\n\t\t\treturn z.number();\n\n\t\tcase \"integer\":\n\t\t\treturn z.number().int();\n\n\t\tcase \"boolean\":\n\t\t\t// Boolean fields map to `INTEGER` columns (`FIELD_TYPE_TO_COLUMN`\n\t\t\t// in `schema/types.ts`) and `serializeValue` in\n\t\t\t// `database/repositories/content.ts` writes booleans as 0/1.\n\t\t\t// `deserializeValue` never converts them back, so reads return\n\t\t\t// numbers. Coerce the stored 0/1 shape here so a GET → POST\n\t\t\t// round-trip on a boolean field passes validation. Other inputs\n\t\t\t// (strings, other numbers) fall through to `z.boolean()` and\n\t\t\t// produce its standard rejection.\n\t\t\treturn z.preprocess((v) => (v === 0 || v === 1 ? Boolean(v) : v), z.boolean());\n\n\t\tcase \"datetime\":\n\t\t\t// Accept every value that legitimately round-trips through the admin\n\t\t\t// and seeds: ISO with `Z`, ISO with a timezone offset, a naive\n\t\t\t// datetime (`YYYY-MM-DDTHH:mm[:ss]` -- what `<input type=\"datetime-local\">`\n\t\t\t// and many seeds produce), and a date-only value. The admin re-sends\n\t\t\t// every loaded field on autosave, so a stored naive datetime must\n\t\t\t// validate or the entry becomes unsavable through its own editor\n\t\t\t// (#1368; same class as #867). `z.iso.*` retains semantic validation,\n\t\t\t// so impossible dates are still rejected.\n\t\t\treturn z.iso.datetime({ offset: true, local: true }).or(z.iso.date());\n\n\t\tcase \"select\": {\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\tconst [first, ...rest] = options;\n\t\t\t\treturn z.enum([first, ...rest]);\n\t\t\t}\n\t\t\treturn z.string();\n\t\t}\n\n\t\tcase \"multiSelect\": {\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\tconst [first, ...rest] = multiOptions;\n\t\t\t\treturn z.array(z.enum([first, ...rest]));\n\t\t\t}\n\t\t\treturn z.array(z.string());\n\t\t}\n\n\t\tcase \"portableText\":\n\t\t\t// Portable Text is an array of blocks. We require `_type` because\n\t\t\t// renderers dispatch on it, but `_key` is intentionally optional:\n\t\t\t// it's a UI-layer concern that the editor regenerates on every\n\t\t\t// change (see `PortableTextEditor`), and the rest of this schema\n\t\t\t// uses `.passthrough()` for everything below the top level. Making\n\t\t\t// `_key` strictly required here was an accidentally tight invariant\n\t\t\t// that rejected any seed/import data not authored against the\n\t\t\t// editor (#867 — autosave failures on seeded template content).\n\t\t\treturn z.array(\n\t\t\t\tz\n\t\t\t\t\t.object({\n\t\t\t\t\t\t_type: z.string(),\n\t\t\t\t\t\t_key: z.string().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.passthrough(),\n\t\t\t);\n\n\t\tcase \"image\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\talt: z.string().optional(),\n\t\t\t\twidth: z.number().optional(),\n\t\t\t\theight: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"cloudflare-images\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Admin-side preview URL for external providers (not persisted by plugins) */\n\t\t\t\tpreviewUrl: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"file\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\tfilename: z.string().optional(),\n\t\t\t\tmimeType: z.string().optional(),\n\t\t\t\tsize: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"s3\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"reference\":\n\t\t\treturn z.string(); // Reference ID\n\n\t\tcase \"json\":\n\t\t\treturn z.unknown();\n\n\t\tdefault:\n\t\t\treturn z.unknown();\n\t}\n}\n\n/**\n * Apply validation rules to a schema\n */\nfunction applyValidation(schema: ZodTypeAny, field: Field): ZodTypeAny {\n\tconst validation = field.validation;\n\tif (!validation) return schema;\n\n\t// String validations\n\tif (schema instanceof z.ZodString) {\n\t\tlet strSchema = schema;\n\t\tif (validation.minLength !== undefined) {\n\t\t\tstrSchema = strSchema.min(validation.minLength);\n\t\t}\n\t\tif (validation.maxLength !== undefined) {\n\t\t\tstrSchema = strSchema.max(validation.maxLength);\n\t\t}\n\t\tif (validation.pattern) {\n\t\t\tstrSchema = strSchema.regex(new RegExp(validation.pattern));\n\t\t}\n\t\treturn strSchema;\n\t}\n\n\t// Number validations\n\tif (schema instanceof z.ZodNumber) {\n\t\tlet numSchema = schema;\n\t\tif (validation.min !== undefined) {\n\t\t\tnumSchema = numSchema.min(validation.min);\n\t\t}\n\t\tif (validation.max !== undefined) {\n\t\t\tnumSchema = numSchema.max(validation.max);\n\t\t}\n\t\treturn numSchema;\n\t}\n\n\treturn schema;\n}\n\n/**\n * Schema cache to avoid regenerating schemas on every request\n */\nconst schemaCache = new Map<string, { schema: z.ZodObject<any>; version: string }>();\n\n/**\n * Get or generate a cached schema for a collection\n */\nexport function getCachedSchema(\n\tcollection: CollectionWithFields,\n\tversion?: string,\n): z.ZodObject<any> {\n\tconst cacheKey = collection.slug;\n\tconst cached = schemaCache.get(cacheKey);\n\n\t// If version matches, return cached schema\n\tif (cached && (!version || cached.version === version)) {\n\t\treturn cached.schema;\n\t}\n\n\t// Generate new schema\n\tconst schema = generateZodSchema(collection);\n\n\t// Cache it\n\tschemaCache.set(cacheKey, {\n\t\tschema,\n\t\tversion: version || collection.updatedAt,\n\t});\n\n\treturn schema;\n}\n\n/**\n * Invalidate cached schema for a collection\n */\nexport function invalidateSchemaCache(slug: string): void {\n\tschemaCache.delete(slug);\n}\n\n/**\n * Clear all cached schemas\n */\nexport function clearSchemaCache(): void {\n\tschemaCache.clear();\n}\n\n/**\n * Validate data against a collection's schema\n */\nexport function validateContent(\n\tcollection: CollectionWithFields,\n\tdata: unknown,\n): { success: true; data: unknown } | { success: false; errors: z.ZodError } {\n\tconst schema = getCachedSchema(collection);\n\n\tconst result = schema.safeParse(data);\n\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\n\treturn { success: false, errors: result.error };\n}\n\n/**\n * Generate TypeScript interface from field definitions\n * Used by CLI `emdash types` to generate types\n */\nexport function generateTypeScript(\n\tcollection: CollectionWithFields,\n\tinterfaceName: string = getInterfaceName(collection),\n): string {\n\tconst lines: string[] = [];\n\n\tlines.push(`export interface ${interfaceName} {`);\n\tlines.push(` id: string;`);\n\tlines.push(` slug: string | null;`);\n\tlines.push(` status: string;`);\n\n\tfor (const field of collection.fields) {\n\t\tconst tsType = fieldTypeToTypeScript(field);\n\t\tconst optional = field.required ? \"\" : \"?\";\n\t\tlines.push(` ${field.slug}${optional}: ${tsType};`);\n\t}\n\n\tlines.push(` createdAt: Date;`);\n\tlines.push(` updatedAt: Date;`);\n\tlines.push(` publishedAt: Date | null;`);\n\t// Bylines are eagerly loaded by getEmDashCollection/getEmDashEntry\n\tlines.push(` bylines?: ContentBylineCredit[];`);\n\t// Taxonomy terms are eagerly loaded by getEmDashCollection/getEmDashEntry,\n\t// keyed by taxonomy name (e.g. data.terms?.tag)\n\tlines.push(` terms?: Record<string, TaxonomyTerm[]>;`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate a complete types file with module augmentation\n * This produces emdash-env.d.ts content that provides typed query functions\n */\nexport function generateTypesFile(collections: CollectionWithFields[]): string {\n\tconst lines: string[] = [];\n\n\t// Header\n\tlines.push(`// Generated by EmDash on dev server start`);\n\tlines.push(`// Do not edit manually`);\n\tlines.push(``);\n\tlines.push(`/// <reference types=\"emdash/locals\" />`);\n\tlines.push(``);\n\n\t// Check if we need PortableTextBlock import\n\tconst needsPortableText = collections.some((c) =>\n\t\tc.fields.some((f) => f.type === \"portableText\"),\n\t);\n\n\t// Build imports - ContentBylineCredit and TaxonomyTerm are always needed\n\t// for the hydrated bylines/terms fields\n\tconst imports = [\"ContentBylineCredit\", \"TaxonomyTerm\"];\n\tif (needsPortableText) {\n\t\timports.push(\"PortableTextBlock\");\n\t}\n\tlines.push(`import type { ${imports.join(\", \")} } from \"emdash\";`);\n\tlines.push(``);\n\n\t// Singularizing the slug can map two distinct slugs to the same name\n\t// (e.g. `book` and `books` both -> `Book`), so resolve collisions up front\n\t// to keep every interface identifier unique within the file.\n\tconst interfaceNames = uniqueInterfaceNames(collections);\n\n\t// Generate individual interfaces\n\tfor (const collection of collections) {\n\t\tlines.push(generateTypeScript(collection, interfaceNames.get(collection.slug)));\n\t\tlines.push(``);\n\t}\n\n\t// Generate the Collections interface for module augmentation\n\tlines.push(`declare module \"emdash\" {`);\n\tlines.push(` interface EmDashCollections {`);\n\tfor (const collection of collections) {\n\t\tlines.push(` ${collection.slug}: ${interfaceNames.get(collection.slug)};`);\n\t}\n\tlines.push(` }`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate schema hash for cache invalidation\n */\nexport async function generateSchemaHash(collections: CollectionWithFields[]): Promise<string> {\n\tconst str = JSON.stringify(\n\t\tcollections.map((c) => ({\n\t\t\tslug: c.slug,\n\t\t\tfields: c.fields.map((f) => ({\n\t\t\t\tslug: f.slug,\n\t\t\t\ttype: f.type,\n\t\t\t\trequired: f.required,\n\t\t\t\tvalidation: f.validation,\n\t\t\t})),\n\t\t})),\n\t);\n\treturn hashString(str);\n}\n\n/**\n * Map field type to TypeScript type\n */\nfunction fieldTypeToTypeScript(field: Field): string {\n\tswitch (field.type) {\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\tcase \"url\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\n\t\tcase \"number\":\n\t\tcase \"integer\":\n\t\t\treturn \"number\";\n\n\t\tcase \"boolean\":\n\t\t\treturn \"boolean\";\n\n\t\tcase \"select\":\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\treturn options.map((o) => `\"${o}\"`).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\n\t\tcase \"multiSelect\":\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\treturn `(${multiOptions.map((o) => `\"${o}\"`).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\n\t\tcase \"portableText\":\n\t\t\treturn \"PortableTextBlock[]\";\n\n\t\tcase \"image\":\n\t\t\treturn \"{ id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"file\":\n\t\t\treturn \"{ id: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"reference\":\n\t\t\t// Could be enhanced to include the referenced collection type\n\t\t\treturn \"string\";\n\n\t\tcase \"json\":\n\t\t\treturn \"unknown\";\n\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Convert string to PascalCase (handles slugs, spaces, etc.)\n */\nfunction pascalCase(str: string): string {\n\treturn str\n\t\t.split(PASCAL_CASE_SPLIT_PATTERN)\n\t\t.filter(Boolean)\n\t\t.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(\"\");\n}\n\n/**\n * Naive singularization for slug-derived interface names. Handles the common\n * English plural endings; intentionally simple, not a full inflector.\n */\nfunction singularize(str: string): string {\n\tif (str.endsWith(\"ies\")) {\n\t\treturn str.slice(0, -3) + \"y\";\n\t}\n\tif (\n\t\tstr.endsWith(\"es\") &&\n\t\t(str.endsWith(\"sses\") || str.endsWith(\"xes\") || str.endsWith(\"ches\") || str.endsWith(\"shes\"))\n\t) {\n\t\treturn str.slice(0, -2);\n\t}\n\tif (str.endsWith(\"s\") && !str.endsWith(\"ss\")) {\n\t\treturn str.slice(0, -1);\n\t}\n\treturn str;\n}\n\n/**\n * Get the interface name for a collection.\n *\n * Derived from the slug, not the human label. Slugs are constrained to\n * `/^[a-z][a-z0-9_]*$/`, so PascalCasing one always yields a valid TS\n * identifier; labels are arbitrary and user-controlled (punctuation, spaces,\n * duplicates across collections), which produced syntactically invalid or\n * duplicate interface names. The slug is singularized first because the\n * interface describes a single entry, not the collection (`posts` -> `Post`).\n *\n * Singularization can map two distinct slugs onto the same name, so callers\n * generating more than one interface must dedupe -- see `uniqueInterfaceNames`.\n */\nfunction getInterfaceName(collection: CollectionWithFields): string {\n\treturn pascalCase(singularize(collection.slug));\n}\n\n/**\n * Resolve interface names for a set of collections, guaranteeing each is\n * unique within the file. Collisions (from singularization or PascalCasing\n * collapsing distinct slugs) get a numeric suffix in collection order, so the\n * generated `.d.ts` never declares two interfaces with the same identifier.\n *\n * The suffix is chosen against the set of names already emitted, not a\n * per-base counter, so a generated name can't collide with another slug's\n * base name (e.g. slugs `book`, `books`, `book2`: `books` -> `Book2` would\n * clash with `book2`, so it advances to `Book3`).\n */\nexport function uniqueInterfaceNames(collections: CollectionWithFields[]): Map<string, string> {\n\tconst used = new Set<string>();\n\tconst names = new Map<string, string>();\n\tfor (const collection of collections) {\n\t\tconst base = getInterfaceName(collection);\n\t\tlet name = base;\n\t\tlet suffix = 2;\n\t\twhile (used.has(name)) {\n\t\t\tname = `${base}${suffix}`;\n\t\t\tsuffix++;\n\t\t}\n\t\tused.add(name);\n\t\tnames.set(collection.slug, name);\n\t}\n\treturn names;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"zod-generator-CacrR-i8.mjs","names":[],"sources":["../src/schema/zod-generator.ts"],"sourcesContent":["import { z, type ZodTypeAny } from \"zod\";\n\nimport { hashString } from \"../utils/hash.js\";\nimport type { Field, FieldType, CollectionWithFields } from \"./types.js\";\n\n/** Pattern to split on underscores, hyphens, and spaces for PascalCase conversion */\nconst PASCAL_CASE_SPLIT_PATTERN = /[_\\-\\s]+/;\n\n/**\n * Generate a Zod schema from a collection's field definitions\n *\n * This allows runtime validation of content based on dynamically\n * defined schemas stored in D1.\n */\nexport function generateZodSchema(\n\tcollection: CollectionWithFields,\n): z.ZodObject<Record<string, ZodTypeAny>> {\n\tconst shape: Record<string, ZodTypeAny> = {};\n\n\tfor (const field of collection.fields) {\n\t\tshape[field.slug] = generateFieldSchema(field);\n\t}\n\n\treturn z.object(shape);\n}\n\n/**\n * Generate Zod schema for a single field\n */\nexport function generateFieldSchema(field: Field): ZodTypeAny {\n\tlet schema = getBaseSchema(field.type, field);\n\n\t// Apply validation rules\n\tif (field.validation) {\n\t\tschema = applyValidation(schema, field);\n\t}\n\n\t// Apply required/optional. Non-required fields use `.nullish()` rather\n\t// than `.optional()` because the underlying SQLite columns are nullable\n\t// (see `SchemaRegistry.addFieldColumn` -- non-required fields are added\n\t// without `NOT NULL`). The admin re-sends what it loaded from the\n\t// server on autosave, so any field that's actually `null` in the DB\n\t// must round-trip cleanly through the validator. `.optional()` only\n\t// accepts `undefined`; `.nullish()` accepts both `undefined` and\n\t// `null`. (#867 — autosave failures on seeded entries.)\n\tif (!field.required) {\n\t\tschema = schema.nullish();\n\t}\n\n\t// Apply default value\n\tif (field.defaultValue !== undefined) {\n\t\tschema = schema.default(field.defaultValue);\n\t}\n\n\treturn schema;\n}\n\n/**\n * Get base Zod schema for a field type\n */\nfunction getBaseSchema(type: FieldType, field: Field): ZodTypeAny {\n\tswitch (type) {\n\t\tcase \"url\":\n\t\t\treturn z.string().url();\n\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\t\treturn z.string();\n\n\t\tcase \"number\":\n\t\t\treturn z.number();\n\n\t\tcase \"integer\":\n\t\t\treturn z.number().int();\n\n\t\tcase \"boolean\":\n\t\t\t// Boolean fields map to `INTEGER` columns (`FIELD_TYPE_TO_COLUMN`\n\t\t\t// in `schema/types.ts`) and `serializeValue` in\n\t\t\t// `database/repositories/content.ts` writes booleans as 0/1.\n\t\t\t// `deserializeValue` never converts them back, so reads return\n\t\t\t// numbers. Coerce the stored 0/1 shape here so a GET → POST\n\t\t\t// round-trip on a boolean field passes validation. Other inputs\n\t\t\t// (strings, other numbers) fall through to `z.boolean()` and\n\t\t\t// produce its standard rejection.\n\t\t\treturn z.preprocess((v) => (v === 0 || v === 1 ? Boolean(v) : v), z.boolean());\n\n\t\tcase \"datetime\":\n\t\t\t// Accept every value that legitimately round-trips through the admin\n\t\t\t// and seeds: ISO with `Z`, ISO with a timezone offset, a naive\n\t\t\t// datetime (`YYYY-MM-DDTHH:mm[:ss]` -- what `<input type=\"datetime-local\">`\n\t\t\t// and many seeds produce), and a date-only value. The admin re-sends\n\t\t\t// every loaded field on autosave, so a stored naive datetime must\n\t\t\t// validate or the entry becomes unsavable through its own editor\n\t\t\t// (#1368; same class as #867). `z.iso.*` retains semantic validation,\n\t\t\t// so impossible dates are still rejected.\n\t\t\treturn z.iso.datetime({ offset: true, local: true }).or(z.iso.date());\n\n\t\tcase \"select\": {\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\tconst [first, ...rest] = options;\n\t\t\t\treturn z.enum([first, ...rest]);\n\t\t\t}\n\t\t\treturn z.string();\n\t\t}\n\n\t\tcase \"multiSelect\": {\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\tconst [first, ...rest] = multiOptions;\n\t\t\t\treturn z.array(z.enum([first, ...rest]));\n\t\t\t}\n\t\t\treturn z.array(z.string());\n\t\t}\n\n\t\tcase \"portableText\":\n\t\t\t// Portable Text is an array of blocks. We require `_type` because\n\t\t\t// renderers dispatch on it, but `_key` is intentionally optional:\n\t\t\t// it's a UI-layer concern that the editor regenerates on every\n\t\t\t// change (see `PortableTextEditor`), and the rest of this schema\n\t\t\t// uses `.passthrough()` for everything below the top level. Making\n\t\t\t// `_key` strictly required here was an accidentally tight invariant\n\t\t\t// that rejected any seed/import data not authored against the\n\t\t\t// editor (#867 — autosave failures on seeded template content).\n\t\t\treturn z.array(\n\t\t\t\tz\n\t\t\t\t\t.object({\n\t\t\t\t\t\t_type: z.string(),\n\t\t\t\t\t\t_key: z.string().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.passthrough(),\n\t\t\t);\n\n\t\tcase \"image\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\talt: z.string().optional(),\n\t\t\t\twidth: z.number().optional(),\n\t\t\t\theight: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"cloudflare-images\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Admin-side preview URL for external providers (not persisted by plugins) */\n\t\t\t\tpreviewUrl: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"file\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\tfilename: z.string().optional(),\n\t\t\t\tmimeType: z.string().optional(),\n\t\t\t\tsize: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"s3\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"reference\":\n\t\t\treturn z.string(); // Reference ID\n\n\t\tcase \"json\":\n\t\t\treturn z.unknown();\n\n\t\tdefault:\n\t\t\treturn z.unknown();\n\t}\n}\n\n/**\n * Apply validation rules to a schema\n */\nfunction applyValidation(schema: ZodTypeAny, field: Field): ZodTypeAny {\n\tconst validation = field.validation;\n\tif (!validation) return schema;\n\n\t// String validations\n\tif (schema instanceof z.ZodString) {\n\t\tlet strSchema = schema;\n\t\tif (validation.minLength !== undefined) {\n\t\t\tstrSchema = strSchema.min(validation.minLength);\n\t\t}\n\t\tif (validation.maxLength !== undefined) {\n\t\t\tstrSchema = strSchema.max(validation.maxLength);\n\t\t}\n\t\tif (validation.pattern) {\n\t\t\tstrSchema = strSchema.regex(new RegExp(validation.pattern));\n\t\t}\n\t\treturn strSchema;\n\t}\n\n\t// Number validations\n\tif (schema instanceof z.ZodNumber) {\n\t\tlet numSchema = schema;\n\t\tif (validation.min !== undefined) {\n\t\t\tnumSchema = numSchema.min(validation.min);\n\t\t}\n\t\tif (validation.max !== undefined) {\n\t\t\tnumSchema = numSchema.max(validation.max);\n\t\t}\n\t\treturn numSchema;\n\t}\n\n\treturn schema;\n}\n\n/**\n * Schema cache to avoid regenerating schemas on every request\n */\nconst schemaCache = new Map<string, { schema: z.ZodObject<any>; version: string }>();\n\n/**\n * Get or generate a cached schema for a collection\n */\nexport function getCachedSchema(\n\tcollection: CollectionWithFields,\n\tversion?: string,\n): z.ZodObject<any> {\n\tconst cacheKey = collection.slug;\n\tconst cached = schemaCache.get(cacheKey);\n\n\t// If version matches, return cached schema\n\tif (cached && (!version || cached.version === version)) {\n\t\treturn cached.schema;\n\t}\n\n\t// Generate new schema\n\tconst schema = generateZodSchema(collection);\n\n\t// Cache it\n\tschemaCache.set(cacheKey, {\n\t\tschema,\n\t\tversion: version || collection.updatedAt,\n\t});\n\n\treturn schema;\n}\n\n/**\n * Invalidate cached schema for a collection\n */\nexport function invalidateSchemaCache(slug: string): void {\n\tschemaCache.delete(slug);\n}\n\n/**\n * Clear all cached schemas\n */\nexport function clearSchemaCache(): void {\n\tschemaCache.clear();\n}\n\n/**\n * Validate data against a collection's schema\n */\nexport function validateContent(\n\tcollection: CollectionWithFields,\n\tdata: unknown,\n): { success: true; data: unknown } | { success: false; errors: z.ZodError } {\n\tconst schema = getCachedSchema(collection);\n\n\tconst result = schema.safeParse(data);\n\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\n\treturn { success: false, errors: result.error };\n}\n\n/**\n * Generate TypeScript interface from field definitions\n * Used by CLI `emdash types` to generate types\n */\nexport function generateTypeScript(\n\tcollection: CollectionWithFields,\n\tinterfaceName: string = getInterfaceName(collection),\n): string {\n\tconst lines: string[] = [];\n\n\tlines.push(`export interface ${interfaceName} {`);\n\tlines.push(` id: string;`);\n\tlines.push(` slug: string | null;`);\n\tlines.push(` status: string;`);\n\n\tfor (const field of collection.fields) {\n\t\tconst tsType = fieldTypeToTypeScript(field);\n\t\tconst optional = field.required ? \"\" : \"?\";\n\t\tlines.push(` ${field.slug}${optional}: ${tsType};`);\n\t}\n\n\tlines.push(` createdAt: Date;`);\n\tlines.push(` updatedAt: Date;`);\n\tlines.push(` publishedAt: Date | null;`);\n\t// Bylines are eagerly loaded by getEmDashCollection/getEmDashEntry\n\tlines.push(` bylines?: ContentBylineCredit[];`);\n\t// Taxonomy terms are eagerly loaded by getEmDashCollection/getEmDashEntry,\n\t// keyed by taxonomy name (e.g. data.terms?.tag)\n\tlines.push(` terms?: Record<string, TaxonomyTerm[]>;`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate a complete types file with module augmentation\n * This produces emdash-env.d.ts content that provides typed query functions\n */\nexport function generateTypesFile(collections: CollectionWithFields[]): string {\n\tconst lines: string[] = [];\n\n\t// Header\n\tlines.push(`// Generated by EmDash on dev server start`);\n\tlines.push(`// Do not edit manually`);\n\tlines.push(``);\n\tlines.push(`/// <reference types=\"emdash/locals\" />`);\n\tlines.push(``);\n\n\t// Check if we need PortableTextBlock import\n\tconst needsPortableText = collections.some((c) =>\n\t\tc.fields.some((f) => f.type === \"portableText\"),\n\t);\n\n\t// Build imports - ContentBylineCredit and TaxonomyTerm are always needed\n\t// for the hydrated bylines/terms fields\n\tconst imports = [\"ContentBylineCredit\", \"TaxonomyTerm\"];\n\tif (needsPortableText) {\n\t\timports.push(\"PortableTextBlock\");\n\t}\n\tlines.push(`import type { ${imports.join(\", \")} } from \"emdash\";`);\n\tlines.push(``);\n\n\t// Singularizing the slug can map two distinct slugs to the same name\n\t// (e.g. `book` and `books` both -> `Book`), so resolve collisions up front\n\t// to keep every interface identifier unique within the file.\n\tconst interfaceNames = uniqueInterfaceNames(collections);\n\n\t// Generate individual interfaces\n\tfor (const collection of collections) {\n\t\tlines.push(generateTypeScript(collection, interfaceNames.get(collection.slug)));\n\t\tlines.push(``);\n\t}\n\n\t// Generate the Collections interface for module augmentation\n\tlines.push(`declare module \"emdash\" {`);\n\tlines.push(` interface EmDashCollections {`);\n\tfor (const collection of collections) {\n\t\tlines.push(` ${collection.slug}: ${interfaceNames.get(collection.slug)};`);\n\t}\n\tlines.push(` }`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate schema hash for cache invalidation\n */\nexport async function generateSchemaHash(collections: CollectionWithFields[]): Promise<string> {\n\tconst str = JSON.stringify(\n\t\tcollections.map((c) => ({\n\t\t\tslug: c.slug,\n\t\t\tfields: c.fields.map((f) => ({\n\t\t\t\tslug: f.slug,\n\t\t\t\ttype: f.type,\n\t\t\t\trequired: f.required,\n\t\t\t\tvalidation: f.validation,\n\t\t\t})),\n\t\t})),\n\t);\n\treturn hashString(str);\n}\n\n/**\n * Map field type to TypeScript type\n */\nfunction fieldTypeToTypeScript(field: Field): string {\n\tswitch (field.type) {\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\tcase \"url\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\n\t\tcase \"number\":\n\t\tcase \"integer\":\n\t\t\treturn \"number\";\n\n\t\tcase \"boolean\":\n\t\t\treturn \"boolean\";\n\n\t\tcase \"select\":\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\treturn options.map((o) => `\"${o}\"`).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\n\t\tcase \"multiSelect\":\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\treturn `(${multiOptions.map((o) => `\"${o}\"`).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\n\t\tcase \"portableText\":\n\t\t\treturn \"PortableTextBlock[]\";\n\n\t\tcase \"image\":\n\t\t\treturn \"{ id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"file\":\n\t\t\treturn \"{ id: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"reference\":\n\t\t\t// Could be enhanced to include the referenced collection type\n\t\t\treturn \"string\";\n\n\t\tcase \"json\":\n\t\t\treturn \"unknown\";\n\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Convert string to PascalCase (handles slugs, spaces, etc.)\n */\nfunction pascalCase(str: string): string {\n\treturn str\n\t\t.split(PASCAL_CASE_SPLIT_PATTERN)\n\t\t.filter(Boolean)\n\t\t.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(\"\");\n}\n\n/**\n * Naive singularization for slug-derived interface names. Handles the common\n * English plural endings; intentionally simple, not a full inflector.\n */\nfunction singularize(str: string): string {\n\tif (str.endsWith(\"ies\")) {\n\t\treturn str.slice(0, -3) + \"y\";\n\t}\n\tif (\n\t\tstr.endsWith(\"es\") &&\n\t\t(str.endsWith(\"sses\") || str.endsWith(\"xes\") || str.endsWith(\"ches\") || str.endsWith(\"shes\"))\n\t) {\n\t\treturn str.slice(0, -2);\n\t}\n\tif (str.endsWith(\"s\") && !str.endsWith(\"ss\")) {\n\t\treturn str.slice(0, -1);\n\t}\n\treturn str;\n}\n\n/**\n * Get the interface name for a collection.\n *\n * Derived from the slug, not the human label. Slugs are constrained to\n * `/^[a-z][a-z0-9_]*$/`, so PascalCasing one always yields a valid TS\n * identifier; labels are arbitrary and user-controlled (punctuation, spaces,\n * duplicates across collections), which produced syntactically invalid or\n * duplicate interface names. The slug is singularized first because the\n * interface describes a single entry, not the collection (`posts` -> `Post`).\n *\n * Singularization can map two distinct slugs onto the same name, so callers\n * generating more than one interface must dedupe -- see `uniqueInterfaceNames`.\n */\nfunction getInterfaceName(collection: CollectionWithFields): string {\n\treturn pascalCase(singularize(collection.slug));\n}\n\n/**\n * Resolve interface names for a set of collections, guaranteeing each is\n * unique within the file. Collisions (from singularization or PascalCasing\n * collapsing distinct slugs) get a numeric suffix in collection order, so the\n * generated `.d.ts` never declares two interfaces with the same identifier.\n *\n * The suffix is chosen against the set of names already emitted, not a\n * per-base counter, so a generated name can't collide with another slug's\n * base name (e.g. slugs `book`, `books`, `book2`: `books` -> `Book2` would\n * clash with `book2`, so it advances to `Book3`).\n */\nexport function uniqueInterfaceNames(collections: CollectionWithFields[]): Map<string, string> {\n\tconst used = new Set<string>();\n\tconst names = new Map<string, string>();\n\tfor (const collection of collections) {\n\t\tconst base = getInterfaceName(collection);\n\t\tlet name = base;\n\t\tlet suffix = 2;\n\t\twhile (used.has(name)) {\n\t\t\tname = `${base}${suffix}`;\n\t\t\tsuffix++;\n\t\t}\n\t\tused.add(name);\n\t\tnames.set(collection.slug, name);\n\t}\n\treturn names;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emdash",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Astro-native CMS with WordPress migration support",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -230,11 +230,11 @@
230
230
  "upng-js": "^2.1.0",
231
231
  "zod": "^4.4.1",
232
232
  "@atcute/client": "^5.0.0",
233
- "@emdash-cms/admin": "0.27.0",
234
- "@emdash-cms/auth": "0.27.0",
235
- "@emdash-cms/gutenberg-to-portable-text": "0.27.0",
236
- "@emdash-cms/registry-client": "0.3.2",
237
- "@emdash-cms/plugin-types": "0.1.0"
233
+ "@emdash-cms/admin": "0.28.0",
234
+ "@emdash-cms/auth": "0.28.0",
235
+ "@emdash-cms/gutenberg-to-portable-text": "0.28.0",
236
+ "@emdash-cms/plugin-types": "0.1.0",
237
+ "@emdash-cms/registry-client": "0.3.2"
238
238
  },
239
239
  "optionalDependencies": {
240
240
  "@libsql/kysely-libsql": "^0.4.0",
@@ -267,7 +267,7 @@
267
267
  "vite": "^6.0.0",
268
268
  "vitest": "^4.1.5",
269
269
  "zod-openapi": "^5.4.6",
270
- "@emdash-cms/blocks": "0.27.0"
270
+ "@emdash-cms/blocks": "0.28.0"
271
271
  },
272
272
  "repository": {
273
273
  "type": "git",
@@ -1062,15 +1062,18 @@ export async function handleContentDelete(
1062
1062
  db: Kysely<Database>,
1063
1063
  collection: string,
1064
1064
  id: string,
1065
- ): Promise<ApiResult<{ deleted: true }>> {
1065
+ ): Promise<ApiResult<{ deleted: true; id: string }>> {
1066
1066
  try {
1067
- const deleted = await withTransaction(db, async (trx) => {
1067
+ const result = await withTransaction(db, async (trx) => {
1068
1068
  const repo = new ContentRepository(trx);
1069
1069
  const resolvedId = (await resolveId(repo, collection, id)) ?? id;
1070
- return repo.delete(collection, resolvedId);
1070
+ return {
1071
+ id: resolvedId,
1072
+ deleted: await repo.delete(collection, resolvedId),
1073
+ };
1071
1074
  });
1072
1075
 
1073
- if (!deleted) {
1076
+ if (!result.deleted) {
1074
1077
  return {
1075
1078
  success: false,
1076
1079
  error: {
@@ -1082,7 +1085,7 @@ export async function handleContentDelete(
1082
1085
 
1083
1086
  return {
1084
1087
  success: true,
1085
- data: { deleted: true },
1088
+ data: { deleted: true, id: result.id },
1086
1089
  };
1087
1090
  } catch (error) {
1088
1091
  console.error("Content delete error:", error);
@@ -1145,7 +1148,7 @@ export async function handleContentPermanentDelete(
1145
1148
  db: Kysely<Database>,
1146
1149
  collection: string,
1147
1150
  id: string,
1148
- ): Promise<ApiResult<{ deleted: true }>> {
1151
+ ): Promise<ApiResult<{ deleted: true; id: string }>> {
1149
1152
  try {
1150
1153
  const repo = new ContentRepository(db);
1151
1154
  const resolvedId = (await resolveIdIncludingTrashed(repo, collection, id)) ?? id;
@@ -1182,7 +1185,7 @@ export async function handleContentPermanentDelete(
1182
1185
 
1183
1186
  return {
1184
1187
  success: true,
1185
- data: { deleted: true },
1188
+ data: { deleted: true, id: resolvedId },
1186
1189
  };
1187
1190
  } catch (error) {
1188
1191
  console.error("Content permanent delete error:", error);
@@ -19,6 +19,25 @@ export const wpPluginExecuteBody = z.object({
19
19
  url: httpUrl,
20
20
  token: z.string().min(1),
21
21
  config: z.record(z.string(), z.unknown()),
22
+ // --- Chunked mode (issue #475). Absent phase = single-shot legacy run. ---
23
+ // The admin drives the import in a loop of bounded requests; cross-chunk
24
+ // state travels with the client so the server stays stateless.
25
+ phase: z.enum(["content", "comments", "finalize"]).optional(),
26
+ /** Position within the phase: post-type index + WP page (content), page (comments) */
27
+ cursor: z
28
+ .object({
29
+ postTypeIndex: z.number().int().min(0).default(0),
30
+ page: z.number().int().min(1).default(1),
31
+ })
32
+ .optional(),
33
+ /** WP post ID -> created EmDash item, accumulated across content chunks */
34
+ idMap: z
35
+ .record(z.string(), z.object({ id: z.string().min(1), collection: z.string().min(1) }))
36
+ .optional(),
37
+ /** Source translation group -> EmDash item ID, accumulated across content chunks */
38
+ translationGroups: z.record(z.string(), z.string().min(1)).optional(),
39
+ /** WP comment ID -> EmDash root comment ID, accumulated across comment chunks */
40
+ commentRoots: z.record(z.string(), z.string().min(1)).optional(),
22
41
  });
23
42
 
24
43
  export const wpPrepareBody = z.object({
@@ -220,14 +220,11 @@ export interface EmDashConfig {
220
220
  *
221
221
  * @example
222
222
  * ```ts
223
- * import { auditLogPlugin } from "@emdash-cms/plugin-audit-log";
224
- * import { webhookNotifierPlugin } from "@emdash-cms/plugin-webhook-notifier";
223
+ * import auditLog from "@emdash-cms/plugin-audit-log";
224
+ * import webhookNotifier from "@emdash-cms/plugin-webhook-notifier";
225
225
  *
226
226
  * emdash({
227
- * plugins: [
228
- * auditLogPlugin(),
229
- * webhookNotifierPlugin({ url: "https://example.com/webhook" }),
230
- * ],
227
+ * plugins: [auditLog, webhookNotifier],
231
228
  * })
232
229
  * ```
233
230
  */
@@ -34,7 +34,7 @@ import { getAuthMode, type ExternalAuthMode } from "../../auth/mode.js";
34
34
  import type { ExternalAuthConfig } from "../../auth/types.js";
35
35
  import { resolveSessionUser } from "../session-user.js";
36
36
  import type { EmDashHandlers } from "../types.js";
37
- import { buildEmDashCsp } from "./csp.js";
37
+ import { buildEmDashCsp, getConfiguredStorageEndpoint } from "./csp.js";
38
38
 
39
39
  declare global {
40
40
  namespace App {
@@ -306,7 +306,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
306
306
  if (!import.meta.env.DEV) {
307
307
  response.headers.set(
308
308
  "Content-Security-Policy",
309
- buildEmDashCsp(context.locals.emdash?.config.experimental?.registry),
309
+ buildEmDashCsp(
310
+ context.locals.emdash?.config.experimental?.registry,
311
+ getConfiguredStorageEndpoint(context.locals.emdash?.config.storage),
312
+ ),
310
313
  );
311
314
  }
312
315
  return response;
@@ -318,7 +321,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
318
321
  if (!import.meta.env.DEV) {
319
322
  response.headers.set(
320
323
  "Content-Security-Policy",
321
- buildEmDashCsp(context.locals.emdash?.config.experimental?.registry),
324
+ buildEmDashCsp(
325
+ context.locals.emdash?.config.experimental?.registry,
326
+ getConfiguredStorageEndpoint(context.locals.emdash?.config.storage),
327
+ ),
322
328
  );
323
329
  }
324
330
 
@@ -9,9 +9,44 @@
9
9
  * may reference external images (migrations, external hosting, embeds).
10
10
  * Plugin security does not rely on img-src -- plugins run in V8 isolates with
11
11
  * no DOM access. connect-src stays at 'self' unless the experimental registry
12
- * is configured, in which case the configured aggregator origin is allowed.
12
+ * and/or the configured storage endpoint (for direct-to-S3 signed uploads)
13
+ * are configured, in which case those origins are allowed too.
13
14
  */
14
15
  import type { RegistryConfigInput } from "../../registry/types.js";
16
+ import type { StorageDescriptor } from "../storage/types.js";
17
+
18
+ /** Entrypoint constant used by the `s3()` adapter (see `astro/storage/adapters.ts`). */
19
+ const S3_ADAPTER_ENTRYPOINT = "emdash/storage/s3";
20
+
21
+ /**
22
+ * Storage entrypoints are free to shape their config however they like, so
23
+ * `endpoint` isn't a known field on `StorageDescriptor["config"]` -- only
24
+ * S3-compatible adapters (R2, S3, Minio, ...) set it. Anything else (e.g.
25
+ * local filesystem storage) simply has no `endpoint` to allow.
26
+ *
27
+ * The `s3()` adapter resolves any field omitted from its config -- including
28
+ * `endpoint` -- from the matching `S3_*` env var at runtime (see
29
+ * `storage/s3.ts`'s `resolveS3Config`). A site configured as `s3({ ... })`
30
+ * with only `S3_ENDPOINT` set has no `endpoint` in the descriptor's config,
31
+ * so fall back to that env var for S3-adapter storage before giving up.
32
+ */
33
+ export function getConfiguredStorageEndpoint(
34
+ storage: StorageDescriptor | undefined,
35
+ ): string | undefined {
36
+ const config = storage?.config;
37
+ if (typeof config === "object" && config !== null && "endpoint" in config) {
38
+ const endpoint = config.endpoint;
39
+ if (typeof endpoint === "string") return endpoint;
40
+ }
41
+
42
+ if (storage?.entrypoint === S3_ADAPTER_ENTRYPOINT) {
43
+ const envEndpoint =
44
+ typeof process !== "undefined" && process.env ? process.env.S3_ENDPOINT : undefined;
45
+ if (envEndpoint) return envEndpoint;
46
+ }
47
+
48
+ return undefined;
49
+ }
15
50
 
16
51
  function getRegistryAggregatorOrigin(
17
52
  registry: RegistryConfigInput | undefined,
@@ -28,10 +63,26 @@ function getRegistryAggregatorOrigin(
28
63
  }
29
64
  }
30
65
 
31
- export function buildEmDashCsp(registry?: RegistryConfigInput): string {
66
+ function getHttpOrigin(rawUrl: string | undefined): string | undefined {
67
+ if (!rawUrl) return undefined;
68
+
69
+ try {
70
+ const url = new URL(rawUrl);
71
+ if (url.protocol !== "http:" && url.protocol !== "https:") return undefined;
72
+ return url.origin;
73
+ } catch {
74
+ return undefined;
75
+ }
76
+ }
77
+
78
+ export function buildEmDashCsp(registry?: RegistryConfigInput, storageEndpoint?: string): string {
32
79
  const connectSrc = ["connect-src 'self'"];
80
+ const origins = new Set<string>();
33
81
  const registryAggregatorOrigin = getRegistryAggregatorOrigin(registry);
34
- if (registryAggregatorOrigin) connectSrc.push(registryAggregatorOrigin);
82
+ if (registryAggregatorOrigin) origins.add(registryAggregatorOrigin);
83
+ const storageOrigin = getHttpOrigin(storageEndpoint);
84
+ if (storageOrigin) origins.add(storageOrigin);
85
+ connectSrc.push(...origins);
35
86
 
36
87
  return [
37
88
  "default-src 'self'",
@@ -24,6 +24,15 @@ const messages = await loadMessages(resolvedLocale);
24
24
 
25
25
  const adminConfig = Astro.locals.emdash?.config?.admin;
26
26
  const pageTitle = adminConfig?.siteName ? `${adminConfig.siteName} Admin` : "EmDash Admin";
27
+
28
+ // The admin shell must never be stored by a shared cache. Without an explicit
29
+ // Cache-Control, caches fall back to RFC 9111 heuristic freshness — e.g.
30
+ // Cloudflare's Workers Cache stores header-less 200s for 2 hours — so an
31
+ // authenticated editor's 200 could be replayed to anonymous visitors instead
32
+ // of the login redirect. The shell is also personalized per request (locale
33
+ // via cookie/Accept-Language). API routes already send `private, no-store`
34
+ // (API_CACHE_HEADERS); this closes the same gap for the admin HTML.
35
+ Astro.response.headers.set("Cache-Control", "private, no-store");
27
36
  ---
28
37
 
29
38
  <!doctype html>
@@ -20,6 +20,7 @@ import { wpRewriteUrlsBody } from "#api/schemas.js";
20
20
  import { validateIdentifier } from "#db/validate.js";
21
21
  import { normalizeMediaValue } from "#media/normalize.js";
22
22
  import type { MediaProvider } from "#media/types.js";
23
+ import { markContentMediaUsageCollectionStaleSafely } from "#media/usage/content-refresh.js";
23
24
  import type { EmDashHandlers } from "#types";
24
25
 
25
26
  import {
@@ -77,11 +78,12 @@ export const POST: APIRoute = async ({ request, locals }) => {
77
78
  }
78
79
  };
79
80
 
80
- async function rewriteUrls(
81
+ export async function rewriteUrls(
81
82
  db: NonNullable<EmDashHandlers["db"]>,
82
83
  urlMap: Record<string, string>,
83
84
  getProvider: (id: string) => MediaProvider | undefined,
84
85
  collections?: string[],
86
+ markUsageCollectionStale: typeof markContentMediaUsageCollectionStaleSafely = markContentMediaUsageCollectionStaleSafely,
85
87
  ): Promise<RewriteUrlsResult> {
86
88
  const { SchemaRegistry } = await import("#schema/registry.js");
87
89
  const registry = new SchemaRegistry(db);
@@ -92,6 +94,18 @@ async function rewriteUrls(
92
94
  urlsRewritten: 0,
93
95
  errors: [],
94
96
  };
97
+ const staleMarkedCollections = new Set<string>();
98
+ const staleMarkFailedCollections = new Set<string>();
99
+ const markCollectionStale = async (collectionSlug: string): Promise<void> => {
100
+ if (staleMarkedCollections.has(collectionSlug)) return;
101
+ const marked = await markUsageCollectionStale(db, collectionSlug, "CONTENT_USAGE_STALE");
102
+ if (marked) {
103
+ staleMarkedCollections.add(collectionSlug);
104
+ staleMarkFailedCollections.delete(collectionSlug);
105
+ } else {
106
+ staleMarkFailedCollections.add(collectionSlug);
107
+ }
108
+ };
95
109
 
96
110
  // Build base URL map for flexible matching
97
111
  const baseMap = buildBaseUrlMap(urlMap);
@@ -102,52 +116,67 @@ async function rewriteUrls(
102
116
  ? allCollections.filter((c) => collections.includes(c.slug))
103
117
  : allCollections;
104
118
 
105
- for (const collection of targetCollections) {
106
- // Get fields that might contain URLs
107
- const fields = await registry.listFields(collection.id);
108
- const portableTextFields = fields.filter((f) => f.type === "portableText");
109
- const stringFields = fields.filter((f) => ["text", "string"].includes(f.type));
110
- // Image and file fields store URLs directly as TEXT
111
- const mediaFields = fields.filter((f) => ["image", "file"].includes(f.type));
112
-
113
- if (portableTextFields.length === 0 && stringFields.length === 0 && mediaFields.length === 0)
114
- continue;
115
-
116
- // Get table name
117
- validateIdentifier(collection.slug, "collection slug");
118
- const tableName = `ec_${collection.slug}`;
119
-
120
- try {
121
- // Query all rows
122
- const rows = await sql<{ id: string; [key: string]: unknown }>`
119
+ try {
120
+ for (const collection of targetCollections) {
121
+ // Get fields that might contain URLs
122
+ const fields = await registry.listFields(collection.id);
123
+ const portableTextFields = fields.filter((f) => f.type === "portableText");
124
+ const stringFields = fields.filter((f) => ["text", "string"].includes(f.type));
125
+ // Image and file fields store URLs directly as TEXT
126
+ const mediaFields = fields.filter((f) => ["image", "file"].includes(f.type));
127
+
128
+ if (portableTextFields.length === 0 && stringFields.length === 0 && mediaFields.length === 0)
129
+ continue;
130
+
131
+ // Get table name
132
+ validateIdentifier(collection.slug, "collection slug");
133
+ const tableName = `ec_${collection.slug}`;
134
+
135
+ try {
136
+ // Query all rows
137
+ const rows = await sql<{ id: string; [key: string]: unknown }>`
123
138
  SELECT * FROM ${sql.ref(tableName)}
124
139
  WHERE deleted_at IS NULL
125
140
  `.execute(db);
126
141
 
127
- for (const row of rows.rows) {
128
- let rowUpdated = false;
129
- const updates: Record<string, unknown> = {};
130
- let rowUrlsRewritten = 0;
142
+ for (const row of rows.rows) {
143
+ let rowUpdated = false;
144
+ const updates: Record<string, unknown> = {};
145
+ let rowUrlsRewritten = 0;
131
146
 
132
- // Handle Portable Text fields - parse JSON and rewrite URLs in blocks
133
- for (const field of portableTextFields) {
134
- const value = row[field.slug];
135
- if (!value || typeof value !== "string") continue;
147
+ // Handle Portable Text fields - parse JSON and rewrite URLs in blocks
148
+ for (const field of portableTextFields) {
149
+ const value = row[field.slug];
150
+ if (!value || typeof value !== "string") continue;
136
151
 
137
- try {
138
- // eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns unknown; validated by Array.isArray below
139
- const blocks = JSON.parse(value) as PortableTextBlock[];
140
- if (!Array.isArray(blocks)) continue;
152
+ try {
153
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns unknown; validated by Array.isArray below
154
+ const blocks = JSON.parse(value) as PortableTextBlock[];
155
+ if (!Array.isArray(blocks)) continue;
141
156
 
142
- const rewriteResult = rewritePortableTextUrls(blocks, urlMap, baseMap);
157
+ const rewriteResult = rewritePortableTextUrls(blocks, urlMap, baseMap);
143
158
 
144
- if (rewriteResult.changed) {
145
- updates[field.slug] = JSON.stringify(blocks);
146
- rowUpdated = true;
147
- rowUrlsRewritten += rewriteResult.urlsRewritten;
159
+ if (rewriteResult.changed) {
160
+ updates[field.slug] = JSON.stringify(blocks);
161
+ rowUpdated = true;
162
+ rowUrlsRewritten += rewriteResult.urlsRewritten;
163
+ }
164
+ } catch {
165
+ // Not valid JSON, try string replacement as fallback
166
+ const stringResult = rewriteStringUrls(value, urlMap, baseMap);
167
+ if (stringResult.changed) {
168
+ updates[field.slug] = stringResult.newValue;
169
+ rowUpdated = true;
170
+ rowUrlsRewritten += stringResult.urlsRewritten;
171
+ }
148
172
  }
149
- } catch {
150
- // Not valid JSON, try string replacement as fallback
173
+ }
174
+
175
+ // Handle string/text fields - simple string replacement
176
+ for (const field of stringFields) {
177
+ const value = row[field.slug];
178
+ if (!value || typeof value !== "string") continue;
179
+
151
180
  const stringResult = rewriteStringUrls(value, urlMap, baseMap);
152
181
  if (stringResult.changed) {
153
182
  updates[field.slug] = stringResult.newValue;
@@ -155,74 +184,69 @@ async function rewriteUrls(
155
184
  rowUrlsRewritten += stringResult.urlsRewritten;
156
185
  }
157
186
  }
158
- }
159
-
160
- // Handle string/text fields - simple string replacement
161
- for (const field of stringFields) {
162
- const value = row[field.slug];
163
- if (!value || typeof value !== "string") continue;
164
-
165
- const stringResult = rewriteStringUrls(value, urlMap, baseMap);
166
- if (stringResult.changed) {
167
- updates[field.slug] = stringResult.newValue;
168
- rowUpdated = true;
169
- rowUrlsRewritten += stringResult.urlsRewritten;
170
- }
171
- }
172
187
 
173
- // Handle image/file fields - normalize to MediaValue objects
174
- for (const field of mediaFields) {
175
- const value = row[field.slug];
176
- if (!value || typeof value !== "string") continue;
177
-
178
- // Values are stored as JSON MediaValue objects (e.g. featured_image from
179
- // import normalizes to {"provider":"external","src":"<wp url>"}). Match on the
180
- // inner `src`, falling back to the raw value for legacy bare-URL rows.
181
- const newUrl = findMatchingUrl(extractMediaUrl(value), urlMap, baseMap);
182
- if (newUrl) {
183
- // Normalize into a proper MediaValue instead of storing a bare URL
184
- try {
185
- const normalized = await normalizeMediaValue(newUrl, getProvider);
186
- updates[field.slug] = normalized ? JSON.stringify(normalized) : newUrl;
187
- } catch {
188
- updates[field.slug] = newUrl;
188
+ // Handle image/file fields - normalize to MediaValue objects
189
+ for (const field of mediaFields) {
190
+ const value = row[field.slug];
191
+ if (!value || typeof value !== "string") continue;
192
+
193
+ // Values are stored as JSON MediaValue objects (e.g. featured_image from
194
+ // import normalizes to {"provider":"external","src":"<wp url>"}). Match on the
195
+ // inner `src`, falling back to the raw value for legacy bare-URL rows.
196
+ const newUrl = findMatchingUrl(extractMediaUrl(value), urlMap, baseMap);
197
+ if (newUrl) {
198
+ // Normalize into a proper MediaValue instead of storing a bare URL
199
+ try {
200
+ const normalized = await normalizeMediaValue(newUrl, getProvider);
201
+ updates[field.slug] = normalized ? JSON.stringify(normalized) : newUrl;
202
+ } catch {
203
+ updates[field.slug] = newUrl;
204
+ }
205
+ rowUpdated = true;
206
+ rowUrlsRewritten++;
189
207
  }
190
- rowUpdated = true;
191
- rowUrlsRewritten++;
192
208
  }
193
- }
194
-
195
- if (rowUpdated) {
196
- try {
197
- // Build update query dynamically
198
- // eslint-disable-next-line typescript/no-unsafe-type-assertion -- Kysely dynamic table requires type assertion
199
- let query = db.updateTable(tableName as any).where("id", "=", row.id);
200
209
 
201
- for (const [key, value] of Object.entries(updates)) {
202
- // eslint-disable-next-line typescript/no-unsafe-type-assertion -- Kysely dynamic column update requires type assertion
203
- query = query.set({ [key]: value } as any);
210
+ if (rowUpdated) {
211
+ try {
212
+ // Build update query dynamically
213
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- Kysely dynamic table requires type assertion
214
+ let query = db.updateTable(tableName as any).where("id", "=", row.id);
215
+
216
+ for (const [key, value] of Object.entries(updates)) {
217
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- Kysely dynamic column update requires type assertion
218
+ query = query.set({ [key]: value } as any);
219
+ }
220
+
221
+ await query.execute();
222
+
223
+ result.updated++;
224
+ result.urlsRewritten += rowUrlsRewritten;
225
+ result.byCollection[collection.slug] =
226
+ (result.byCollection[collection.slug] || 0) + 1;
227
+ await markCollectionStale(collection.slug);
228
+ } catch (updateError) {
229
+ result.errors.push({
230
+ collection: collection.slug,
231
+ id: row.id,
232
+ error: updateError instanceof Error ? updateError.message : "Update failed",
233
+ });
204
234
  }
205
-
206
- await query.execute();
207
-
208
- result.updated++;
209
- result.urlsRewritten += rowUrlsRewritten;
210
- result.byCollection[collection.slug] = (result.byCollection[collection.slug] || 0) + 1;
211
- } catch (updateError) {
212
- result.errors.push({
213
- collection: collection.slug,
214
- id: row.id,
215
- error: updateError instanceof Error ? updateError.message : "Update failed",
216
- });
217
235
  }
218
236
  }
237
+ } catch (queryError) {
238
+ result.errors.push({
239
+ collection: collection.slug,
240
+ id: "*",
241
+ error: queryError instanceof Error ? queryError.message : "Query failed for collection",
242
+ });
219
243
  }
220
- } catch (queryError) {
221
- result.errors.push({
222
- collection: collection.slug,
223
- id: "*",
224
- error: queryError instanceof Error ? queryError.message : "Query failed for collection",
225
- });
244
+ }
245
+ } finally {
246
+ for (const collectionSlug of staleMarkFailedCollections) {
247
+ if (staleMarkedCollections.has(collectionSlug)) continue;
248
+ const marked = await markUsageCollectionStale(db, collectionSlug, "CONTENT_USAGE_STALE");
249
+ if (marked) staleMarkedCollections.add(collectionSlug);
226
250
  }
227
251
  }
228
252