emdash 0.27.0 → 0.28.1

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 (537) 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-DxFSorgk.mjs} +33 -23
  11. package/dist/api-DxFSorgk.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-CDJqIjPU.mjs} +111 -74
  15. package/dist/apply-CDJqIjPU.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_.d.mts.map +1 -1
  177. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +6 -14
  178. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
  179. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  180. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  181. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  182. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  183. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  184. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  185. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +36 -35
  186. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  187. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +36 -35
  188. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  189. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +36 -35
  190. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  191. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +36 -35
  192. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  193. package/dist/astro/routes/api/schema/collections/index.mjs +36 -35
  194. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  195. package/dist/astro/routes/api/schema/index.mjs +7 -6
  196. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  197. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +36 -35
  198. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  199. package/dist/astro/routes/api/schema/orphans/index.mjs +36 -35
  200. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  201. package/dist/astro/routes/api/search/enable.mjs +10 -10
  202. package/dist/astro/routes/api/search/index.mjs +9 -9
  203. package/dist/astro/routes/api/search/rebuild.mjs +10 -10
  204. package/dist/astro/routes/api/search/stats.mjs +6 -6
  205. package/dist/astro/routes/api/search/suggest.mjs +9 -9
  206. package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
  207. package/dist/astro/routes/api/sections/index.mjs +9 -9
  208. package/dist/astro/routes/api/settings/email.mjs +4 -4
  209. package/dist/astro/routes/api/settings.mjs +12 -12
  210. package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
  211. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  212. package/dist/astro/routes/api/setup/dev-bypass.mjs +25 -24
  213. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  214. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  215. package/dist/astro/routes/api/setup/index.mjs +26 -25
  216. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  217. package/dist/astro/routes/api/setup/status.mjs +4 -4
  218. package/dist/astro/routes/api/snapshot.mjs +6 -6
  219. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
  220. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
  221. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
  222. package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
  223. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  224. package/dist/astro/routes/api/typegen.mjs +5 -5
  225. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  226. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  227. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  228. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  229. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -9
  230. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -9
  231. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  232. package/dist/astro/routes/api/widget-areas/index.mjs +9 -9
  233. package/dist/astro/routes/api/widget-components.mjs +3 -3
  234. package/dist/astro/routes/robots.txt.mjs +6 -6
  235. package/dist/astro/routes/sitemap-_collection_.xml.mjs +9 -9
  236. package/dist/astro/routes/sitemap.xml.mjs +7 -7
  237. package/dist/astro/types.d.mts +13 -13
  238. package/dist/auth/providers/github.d.mts +1 -1
  239. package/dist/auth/providers/google.d.mts +1 -1
  240. package/dist/{authorize-C3a_zGsM.mjs → authorize-Dz88F_Fw.mjs} +2 -2
  241. package/dist/{authorize-C3a_zGsM.mjs.map → authorize-Dz88F_Fw.mjs.map} +1 -1
  242. package/dist/{byline-DEKSVPTy.mjs → byline-Bm8ZgUP9.mjs} +6 -6
  243. package/dist/{byline-DEKSVPTy.mjs.map → byline-Bm8ZgUP9.mjs.map} +1 -1
  244. package/dist/{byline-fields-eyJMblSr.mjs → byline-fields-2Lp5Hbgf.mjs} +18 -3
  245. package/dist/byline-fields-2Lp5Hbgf.mjs.map +1 -0
  246. package/dist/{byline-fields-CtQ6blXd.d.mts → byline-fields-CHE8UStY.d.mts} +47 -32
  247. package/dist/{byline-fields-CtQ6blXd.d.mts.map → byline-fields-CHE8UStY.d.mts.map} +1 -1
  248. package/dist/{byline-fields-DaMKzkhO.mjs → byline-fields-CIx_GuN4.mjs} +1 -1
  249. package/dist/{byline-fields-DaMKzkhO.mjs.map → byline-fields-CIx_GuN4.mjs.map} +1 -1
  250. package/dist/{bylines-Ds4-otXl.mjs → bylines-6tm9mgQb.mjs} +4 -4
  251. package/dist/{bylines-Ds4-otXl.mjs.map → bylines-6tm9mgQb.mjs.map} +1 -1
  252. package/dist/{bylines-CiqgTHWF.mjs → bylines-CAZxGSrm.mjs} +6 -6
  253. package/dist/{bylines-CiqgTHWF.mjs.map → bylines-CAZxGSrm.mjs.map} +1 -1
  254. package/dist/{cache-Ciu3e3AF.mjs → cache-CEBAZjir.mjs} +2 -2
  255. package/dist/{cache-Ciu3e3AF.mjs.map → cache-CEBAZjir.mjs.map} +1 -1
  256. package/dist/{challenge-store-LhiqMccz.mjs → challenge-store-anhwVER0.mjs} +1 -1
  257. package/dist/{challenge-store-LhiqMccz.mjs.map → challenge-store-anhwVER0.mjs.map} +1 -1
  258. package/dist/{chunks-CZFiauig.mjs → chunks-B2isWQhr.mjs} +2 -2
  259. package/dist/{chunks-CZFiauig.mjs.map → chunks-B2isWQhr.mjs.map} +1 -1
  260. package/dist/cli/index.mjs +22 -21
  261. package/dist/cli/index.mjs.map +1 -1
  262. package/dist/client/cf-access.d.mts +1 -1
  263. package/dist/client/index.d.mts +1 -1
  264. package/dist/client/index.mjs +2 -2
  265. package/dist/{comment-CH1IrFFs.mjs → comment-CzeatwBV.mjs} +3 -3
  266. package/dist/{comment-CH1IrFFs.mjs.map → comment-CzeatwBV.mjs.map} +1 -1
  267. package/dist/{comment-reaction-kruNY-kP.mjs → comment-reaction-Cj9QJY7I.mjs} +2 -2
  268. package/dist/{comment-reaction-kruNY-kP.mjs.map → comment-reaction-Cj9QJY7I.mjs.map} +1 -1
  269. package/dist/{comments-BUJyXbgq.mjs → comments-BLE-jv5k.mjs} +3 -3
  270. package/dist/{comments-BUJyXbgq.mjs.map → comments-BLE-jv5k.mjs.map} +1 -1
  271. package/dist/{components-CYt4uVK9.mjs → components-B_sATNUc.mjs} +1 -1
  272. package/dist/{components-CYt4uVK9.mjs.map → components-B_sATNUc.mjs.map} +1 -1
  273. package/dist/{content-RmfHoWqK.mjs → content-DhgEth4k.mjs} +4 -4
  274. package/dist/{content-RmfHoWqK.mjs.map → content-DhgEth4k.mjs.map} +1 -1
  275. package/dist/content-refresh-Br4gMzh8.mjs +1537 -0
  276. package/dist/content-refresh-Br4gMzh8.mjs.map +1 -0
  277. package/dist/{context-CzUU_WzL.mjs → context-BRIpHARC.mjs} +76 -54
  278. package/dist/context-BRIpHARC.mjs.map +1 -0
  279. package/dist/{cron-C5LVoNmP.mjs → cron-v4FTEFR_.mjs} +1 -1
  280. package/dist/{cron-C5LVoNmP.mjs.map → cron-v4FTEFR_.mjs.map} +1 -1
  281. package/dist/{dashboard-CVyGhift.mjs → dashboard-EvEKF5en.mjs} +4 -4
  282. package/dist/{dashboard-CVyGhift.mjs.map → dashboard-EvEKF5en.mjs.map} +1 -1
  283. package/dist/db/index.d.mts +3 -3
  284. package/dist/db/index.mjs +1 -1
  285. package/dist/db/libsql.d.mts +1 -1
  286. package/dist/db/postgres.d.mts +1 -1
  287. package/dist/db/sqlite.d.mts +1 -1
  288. package/dist/{default-CeRG-Ot4.mjs → default-CM_D83Mg.mjs} +1 -1
  289. package/dist/{default-CeRG-Ot4.mjs.map → default-CM_D83Mg.mjs.map} +1 -1
  290. package/dist/{device-flow-DENDCQ9F.mjs → device-flow-CfGP18Uw.mjs} +4 -4
  291. package/dist/{device-flow-DENDCQ9F.mjs.map → device-flow-CfGP18Uw.mjs.map} +1 -1
  292. package/dist/{email-console-DJP32ucW.mjs → email-console-CIf4Lfvv.mjs} +1 -1
  293. package/dist/{email-console-DJP32ucW.mjs.map → email-console-CIf4Lfvv.mjs.map} +1 -1
  294. package/dist/{error-BHkLZk-w.mjs → error-CkAseSqZ.mjs} +2 -2
  295. package/dist/{error-BHkLZk-w.mjs.map → error-CkAseSqZ.mjs.map} +1 -1
  296. package/dist/{escape-Bjio4ZsM.mjs → escape-DqD0aCKu.mjs} +1 -1
  297. package/dist/{escape-Bjio4ZsM.mjs.map → escape-DqD0aCKu.mjs.map} +1 -1
  298. package/dist/{field-defs-cache-BA-8ZPiT.mjs → field-defs-cache-WUI88LX5.mjs} +2 -2
  299. package/dist/{field-defs-cache-BA-8ZPiT.mjs.map → field-defs-cache-WUI88LX5.mjs.map} +1 -1
  300. package/dist/{fts-manager-Db9jGavf.mjs → fts-manager-BbFaF_V8.mjs} +2 -2
  301. package/dist/{fts-manager-Db9jGavf.mjs.map → fts-manager-BbFaF_V8.mjs.map} +1 -1
  302. package/dist/{import-Bb1T9WJS.mjs → import-fYLf14Co.mjs} +514 -25
  303. package/dist/import-fYLf14Co.mjs.map +1 -0
  304. package/dist/{index-CDZZjrLT.d.mts → index-B9sIxwe4.d.mts} +21 -22
  305. package/dist/{index-CDZZjrLT.d.mts.map → index-B9sIxwe4.d.mts.map} +1 -1
  306. package/dist/{index-DGIjmUXQ.d.mts → index-Bm-Gy8Mh.d.mts} +25 -4
  307. package/dist/index-Bm-Gy8Mh.d.mts.map +1 -0
  308. package/dist/index.d.mts +18 -18
  309. package/dist/index.mjs +54 -53
  310. package/dist/{load-DpHICVPh.mjs → load-Djn3HWQS.mjs} +2 -2
  311. package/dist/{load-DpHICVPh.mjs.map → load-Djn3HWQS.mjs.map} +1 -1
  312. package/dist/{loader-4K37qA-y.mjs → loader-CB6YHeul.mjs} +6 -5
  313. package/dist/loader-CB6YHeul.mjs.map +1 -0
  314. package/dist/{manifest-schema-kPGX7VS-.mjs → manifest-schema-BHlgZM1W.mjs} +1 -1
  315. package/dist/{manifest-schema-kPGX7VS-.mjs.map → manifest-schema-BHlgZM1W.mjs.map} +1 -1
  316. package/dist/media/index.d.mts +1 -1
  317. package/dist/media/local-runtime.d.mts +12 -12
  318. package/dist/media/local-runtime.mjs +5 -5
  319. package/dist/{media-C0nRRfs1.mjs → media-BrXIaMFN.mjs} +2 -2
  320. package/dist/{media-C0nRRfs1.mjs.map → media-BrXIaMFN.mjs.map} +1 -1
  321. package/dist/{media-allowlist-Du8t1u6a.mjs → media-allowlist-CRHEeJ_i.mjs} +1 -1
  322. package/dist/{media-allowlist-Du8t1u6a.mjs.map → media-allowlist-CRHEeJ_i.mjs.map} +1 -1
  323. package/dist/{media-url-O4rm9-SQ.mjs → media-url-CKi5XSw4.mjs} +1 -1
  324. package/dist/{media-url-O4rm9-SQ.mjs.map → media-url-CKi5XSw4.mjs.map} +1 -1
  325. package/dist/{menus-SL2K0xq2.mjs → menus-Cfivc2Ad.mjs} +3 -3
  326. package/dist/{menus-SL2K0xq2.mjs.map → menus-Cfivc2Ad.mjs.map} +1 -1
  327. package/dist/{menus-DzRyC1ID.mjs → menus-b2T4clbO.mjs} +16 -12
  328. package/dist/{menus-DzRyC1ID.mjs.map → menus-b2T4clbO.mjs.map} +1 -1
  329. package/dist/{mode-BB0F8xTC.mjs → mode-BM9QgEmt.mjs} +1 -1
  330. package/dist/{mode-BB0F8xTC.mjs.map → mode-BM9QgEmt.mjs.map} +1 -1
  331. package/dist/{oauth-authorization-CsvzIp_F.mjs → oauth-authorization-CXWkIGT4.mjs} +4 -4
  332. package/dist/{oauth-authorization-CsvzIp_F.mjs.map → oauth-authorization-CXWkIGT4.mjs.map} +1 -1
  333. package/dist/{oauth-clients-C9SYwEbZ.mjs → oauth-clients-BmTI5rsl.mjs} +1 -1
  334. package/dist/{oauth-clients-C9SYwEbZ.mjs.map → oauth-clients-BmTI5rsl.mjs.map} +1 -1
  335. package/dist/{oauth-state-store---zrApfB.mjs → oauth-state-store-YLFLcH5F.mjs} +1 -1
  336. package/dist/{oauth-state-store---zrApfB.mjs.map → oauth-state-store-YLFLcH5F.mjs.map} +1 -1
  337. package/dist/{oauth-user-lookup-SHsWRlG9.mjs → oauth-user-lookup-Bsc_GsX-.mjs} +1 -1
  338. package/dist/{oauth-user-lookup-SHsWRlG9.mjs.map → oauth-user-lookup-Bsc_GsX-.mjs.map} +1 -1
  339. package/dist/object-cache/memory.d.mts +1 -1
  340. package/dist/{object-cache-CHbHv83-.mjs → object-cache-Bok5j2ae.mjs} +43 -3
  341. package/dist/object-cache-Bok5j2ae.mjs.map +1 -0
  342. package/dist/{options-41nCWqi9.d.mts → options-iMpRRxIe.d.mts} +3 -3
  343. package/dist/{options-41nCWqi9.d.mts.map → options-iMpRRxIe.d.mts.map} +1 -1
  344. package/dist/page/index.d.mts +2 -2
  345. package/dist/{parse-CqyZj3Hp.mjs → parse-GmjecGct.mjs} +2 -2
  346. package/dist/{parse-CqyZj3Hp.mjs.map → parse-GmjecGct.mjs.map} +1 -1
  347. package/dist/{passkey-config-C0YfSBko.mjs → passkey-config-De0ahnRg.mjs} +1 -1
  348. package/dist/{passkey-config-C0YfSBko.mjs.map → passkey-config-De0ahnRg.mjs.map} +1 -1
  349. package/dist/{placeholder-Cuce9U-m.d.mts → placeholder-BSKwzRe4.d.mts} +1 -1
  350. package/dist/{placeholder-Cuce9U-m.d.mts.map → placeholder-BSKwzRe4.d.mts.map} +1 -1
  351. package/dist/plugin-types.d.mts +1 -1
  352. package/dist/plugin-utils.d.mts +10 -10
  353. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  354. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  355. package/dist/{portable-text-BICg8bTk.mjs → portable-text-cCa0cGV6.mjs} +1 -1
  356. package/dist/{portable-text-BICg8bTk.mjs.map → portable-text-cCa0cGV6.mjs.map} +1 -1
  357. package/dist/{preview-DKGCt2_p.mjs → preview-SSXNVPAC.mjs} +2 -2
  358. package/dist/{preview-DKGCt2_p.mjs.map → preview-SSXNVPAC.mjs.map} +1 -1
  359. package/dist/{public-url-CTVqgMmg.mjs → public-url-Danv47cS.mjs} +1 -1
  360. package/dist/{public-url-CTVqgMmg.mjs.map → public-url-Danv47cS.mjs.map} +1 -1
  361. package/dist/{query-B0M-9LYR.mjs → query-1Cp6U9oo.mjs} +14 -14
  362. package/dist/{query-B0M-9LYR.mjs.map → query-1Cp6U9oo.mjs.map} +1 -1
  363. package/dist/{rate-limit-BwoUdIpC.mjs → rate-limit-C_Lf63Dd.mjs} +2 -2
  364. package/dist/{rate-limit-BwoUdIpC.mjs.map → rate-limit-C_Lf63Dd.mjs.map} +1 -1
  365. package/dist/{redirect-CS-PHtNh.mjs → redirect-CoW3G-0U.mjs} +1 -1
  366. package/dist/{redirect-CS-PHtNh.mjs.map → redirect-CoW3G-0U.mjs.map} +1 -1
  367. package/dist/{redirect-lMOzG04S.mjs → redirect-IyfNirgS.mjs} +2 -2
  368. package/dist/{redirect-lMOzG04S.mjs.map → redirect-IyfNirgS.mjs.map} +1 -1
  369. package/dist/{redirects-9218MmYl.mjs → redirects-Bs1g42Ri.mjs} +4 -4
  370. package/dist/{redirects-9218MmYl.mjs.map → redirects-Bs1g42Ri.mjs.map} +1 -1
  371. package/dist/{redirects-Dcbuisoj.mjs → redirects-DXEXBAII.mjs} +2 -2
  372. package/dist/{redirects-Dcbuisoj.mjs.map → redirects-DXEXBAII.mjs.map} +1 -1
  373. package/dist/{registry-DP-iEtdR.mjs → registry-B57vp_X9.mjs} +122 -79
  374. package/dist/registry-B57vp_X9.mjs.map +1 -0
  375. package/dist/{request-meta-CmS1tDFf.mjs → request-meta-DbvXaAvU.mjs} +2 -2
  376. package/dist/{request-meta-CmS1tDFf.mjs.map → request-meta-DbvXaAvU.mjs.map} +1 -1
  377. package/dist/{resolve-C7I0qiR0.mjs → resolve-DR3wqMTy.mjs} +1 -1
  378. package/dist/{resolve-C7I0qiR0.mjs.map → resolve-DR3wqMTy.mjs.map} +1 -1
  379. package/dist/{runner-BbR3DfrL.d.mts → runner-DvFWoz8-.d.mts} +2 -2
  380. package/dist/{runner-BbR3DfrL.d.mts.map → runner-DvFWoz8-.d.mts.map} +1 -1
  381. package/dist/{runner-CFngB4WZ.mjs → runner-K8XK101B.mjs} +309 -190
  382. package/dist/runner-K8XK101B.mjs.map +1 -0
  383. package/dist/runtime.d.mts +11 -11
  384. package/dist/runtime.mjs +2 -2
  385. package/dist/{scheduled-publish-BMljtY5G.d.mts → scheduled-publish-Drffrf2a.d.mts} +1 -1
  386. package/dist/scheduled-publish-Drffrf2a.d.mts.map +1 -0
  387. package/dist/{schema-BfJ7bIXH.mjs → schema-CX9cfNkZ.mjs} +5 -5
  388. package/dist/{schema-BfJ7bIXH.mjs.map → schema-CX9cfNkZ.mjs.map} +1 -1
  389. package/dist/{search-C7t3PH1J.mjs → search-DZN2HZ-V.mjs} +4 -4
  390. package/dist/{search-C7t3PH1J.mjs.map → search-DZN2HZ-V.mjs.map} +1 -1
  391. package/dist/{secrets-BSf9pRRY.mjs → secrets-kp760LlD.mjs} +2 -2
  392. package/dist/{secrets-BSf9pRRY.mjs.map → secrets-kp760LlD.mjs.map} +1 -1
  393. package/dist/{sections-Dm0CVX23.mjs → sections-CpF5SaGr.mjs} +3 -3
  394. package/dist/{sections-Dm0CVX23.mjs.map → sections-CpF5SaGr.mjs.map} +1 -1
  395. package/dist/seed/index.d.mts +2 -2
  396. package/dist/seed/index.mjs +19 -18
  397. package/dist/seo/index.d.mts +1 -1
  398. package/dist/seo/index.mjs +1 -1
  399. package/dist/{seo-BkhuuaaE.mjs → seo-89-53DDs.mjs} +1 -1
  400. package/dist/{seo-BkhuuaaE.mjs.map → seo-89-53DDs.mjs.map} +1 -1
  401. package/dist/{seo-Dd3tbY_k.mjs → seo-DXu2UQnC.mjs} +3 -3
  402. package/dist/{seo-Dd3tbY_k.mjs.map → seo-DXu2UQnC.mjs.map} +1 -1
  403. package/dist/{service-CTrqp_Jy.mjs → service-DQmiBMzt.mjs} +3 -3
  404. package/dist/{service-CTrqp_Jy.mjs.map → service-DQmiBMzt.mjs.map} +1 -1
  405. package/dist/{session-user-B8aLtKAH.mjs → session-user-Cascbax7.mjs} +1 -1
  406. package/dist/{session-user-B8aLtKAH.mjs.map → session-user-Cascbax7.mjs.map} +1 -1
  407. package/dist/{settings-CZ7ZP9cW.mjs → settings-rZyHEcNT.mjs} +3 -3
  408. package/dist/{settings-CZ7ZP9cW.mjs.map → settings-rZyHEcNT.mjs.map} +1 -1
  409. package/dist/{settings-BX8cWBus.mjs → settings-zhmdKyJQ.mjs} +5 -5
  410. package/dist/{settings-BX8cWBus.mjs.map → settings-zhmdKyJQ.mjs.map} +1 -1
  411. package/dist/{setup-complete-gEiySUc-.mjs → setup-complete-Cb3LRyzy.mjs} +1 -1
  412. package/dist/{setup-complete-gEiySUc-.mjs.map → setup-complete-Cb3LRyzy.mjs.map} +1 -1
  413. package/dist/{setup-nonce-DzS50zme.mjs → setup-nonce-Z8x0tHf8.mjs} +1 -1
  414. package/dist/{setup-nonce-DzS50zme.mjs.map → setup-nonce-Z8x0tHf8.mjs.map} +1 -1
  415. package/dist/{single-flight-cache-Cdfkic3t.mjs → single-flight-cache-BG3B_GhN.mjs} +1 -1
  416. package/dist/{single-flight-cache-Cdfkic3t.mjs.map → single-flight-cache-BG3B_GhN.mjs.map} +1 -1
  417. package/dist/{site-url-BLebyON8.mjs → site-url-BBfDTgZB.mjs} +1 -1
  418. package/dist/{site-url-BLebyON8.mjs.map → site-url-BBfDTgZB.mjs.map} +1 -1
  419. package/dist/{ssrf-BRKb343l.mjs → ssrf-1g7usGBg.mjs} +1 -1
  420. package/dist/{ssrf-BRKb343l.mjs.map → ssrf-1g7usGBg.mjs.map} +1 -1
  421. package/dist/{status-vUK0SA17.mjs → status-CqJQX1QN.mjs} +1 -1
  422. package/dist/{status-vUK0SA17.mjs.map → status-CqJQX1QN.mjs.map} +1 -1
  423. package/dist/storage/local.d.mts +1 -1
  424. package/dist/storage/local.mjs +1 -1
  425. package/dist/storage/s3.d.mts +1 -1
  426. package/dist/storage/s3.mjs +1 -1
  427. package/dist/{taxonomies-DChKcVxf.mjs → taxonomies-Bs13SN-F.mjs} +4 -4
  428. package/dist/{taxonomies-DChKcVxf.mjs.map → taxonomies-Bs13SN-F.mjs.map} +1 -1
  429. package/dist/{taxonomies-ckPjz-6d.mjs → taxonomies-kH9ABDUF.mjs} +7 -7
  430. package/dist/{taxonomies-ckPjz-6d.mjs.map → taxonomies-kH9ABDUF.mjs.map} +1 -1
  431. package/dist/{taxonomy-Cru-he6s.mjs → taxonomy-BxGxZZdu.mjs} +4 -4
  432. package/dist/{taxonomy-Cru-he6s.mjs.map → taxonomy-BxGxZZdu.mjs.map} +1 -1
  433. package/dist/{tokens-LeuXF9gG.mjs → tokens-BGT0TUUo.mjs} +1 -1
  434. package/dist/{tokens-LeuXF9gG.mjs.map → tokens-BGT0TUUo.mjs.map} +1 -1
  435. package/dist/{transport-Blrl2k_o.d.mts → transport-BMDWHSgZ.d.mts} +1 -1
  436. package/dist/{transport-Blrl2k_o.d.mts.map → transport-BMDWHSgZ.d.mts.map} +1 -1
  437. package/dist/{transport-_2nBz7e9.mjs → transport-DPefPMcz.mjs} +1 -1
  438. package/dist/{transport-_2nBz7e9.mjs.map → transport-DPefPMcz.mjs.map} +1 -1
  439. package/dist/{trusted-proxy-DZY5WCn2.mjs → trusted-proxy-VTPRiX2s.mjs} +1 -1
  440. package/dist/{trusted-proxy-DZY5WCn2.mjs.map → trusted-proxy-VTPRiX2s.mjs.map} +1 -1
  441. package/dist/{types-ETmO_jQr.d.mts → types-BcLUUbbR.d.mts} +2 -2
  442. package/dist/{types-ETmO_jQr.d.mts.map → types-BcLUUbbR.d.mts.map} +1 -1
  443. package/dist/{types-BYnDBVwe.d.mts → types-CGbYowGQ.d.mts} +2 -2
  444. package/dist/{types-BYnDBVwe.d.mts.map → types-CGbYowGQ.d.mts.map} +1 -1
  445. package/dist/{types-CvuKO5Pn.d.mts → types-CgvhU-hF.d.mts} +1 -1
  446. package/dist/{types-CvuKO5Pn.d.mts.map → types-CgvhU-hF.d.mts.map} +1 -1
  447. package/dist/{types-Y09-wtyU.d.mts → types-ClUkdbWb.d.mts} +1 -1
  448. package/dist/{types-Y09-wtyU.d.mts.map → types-ClUkdbWb.d.mts.map} +1 -1
  449. package/dist/{types-DU4TSFS5.mjs → types-CuW0zWDA.mjs} +2 -2
  450. package/dist/{types-DU4TSFS5.mjs.map → types-CuW0zWDA.mjs.map} +1 -1
  451. package/dist/{types-BkZ8DUEI.d.mts → types-DmHk7vAt.d.mts} +1 -1
  452. package/dist/{types-BkZ8DUEI.d.mts.map → types-DmHk7vAt.d.mts.map} +1 -1
  453. package/dist/{types-CQAugunJ.mjs → types-Jo3UYd6b.mjs} +1 -1
  454. package/dist/{types-CQAugunJ.mjs.map → types-Jo3UYd6b.mjs.map} +1 -1
  455. package/dist/{types-xIfVRNLp.d.mts → types-_HFLRzTn.d.mts} +22 -1
  456. package/dist/types-_HFLRzTn.d.mts.map +1 -0
  457. package/dist/{types-ByChcBgE.d.mts → types-ea9sAPck.d.mts} +1 -1
  458. package/dist/{types-ByChcBgE.d.mts.map → types-ea9sAPck.d.mts.map} +1 -1
  459. package/dist/{types-CNlaBFzx.d.mts → types-g_aFIdW0.d.mts} +1 -1
  460. package/dist/{types-CNlaBFzx.d.mts.map → types-g_aFIdW0.d.mts.map} +1 -1
  461. package/dist/{types-Dbqff978.d.mts → types-vgEWF18A.d.mts} +19 -1
  462. package/dist/{types-Dbqff978.d.mts.map → types-vgEWF18A.d.mts.map} +1 -1
  463. package/dist/{user-DgGbKL9s.mjs → user-BjAOvU3Z.mjs} +3 -3
  464. package/dist/{user-DgGbKL9s.mjs.map → user-BjAOvU3Z.mjs.map} +1 -1
  465. package/dist/{utils-B7A57fm9.mjs → utils-BJXElxdw.mjs} +156 -7
  466. package/dist/utils-BJXElxdw.mjs.map +1 -0
  467. package/dist/{validate-CRkcYZAR.mjs → validate-CWSnKVIJ.mjs} +2 -2
  468. package/dist/{validate-CRkcYZAR.mjs.map → validate-CWSnKVIJ.mjs.map} +1 -1
  469. package/dist/{validate-9ECmtEpJ.d.mts → validate-Z1b86_3B.d.mts} +5 -5
  470. package/dist/{validate-9ECmtEpJ.d.mts.map → validate-Z1b86_3B.d.mts.map} +1 -1
  471. package/dist/{validation-sR26SOwD.mjs → validation-0l8-RYj4.mjs} +8 -5
  472. package/dist/{validation-sR26SOwD.mjs.map → validation-0l8-RYj4.mjs.map} +1 -1
  473. package/dist/version-qmeO1XZY.mjs +7 -0
  474. package/dist/{version-CrqhKGWn.mjs.map → version-qmeO1XZY.mjs.map} +1 -1
  475. package/dist/{widgets-DRgtn7Cl.mjs → widgets-hWcIicX8.mjs} +3 -3
  476. package/dist/{widgets-DRgtn7Cl.mjs.map → widgets-hWcIicX8.mjs.map} +1 -1
  477. package/dist/wxr-taxonomies-0_uxSrmf.d.mts +34 -0
  478. package/dist/wxr-taxonomies-0_uxSrmf.d.mts.map +1 -0
  479. package/dist/wxr-taxonomies-CNFcsNCr.mjs +425 -0
  480. package/dist/wxr-taxonomies-CNFcsNCr.mjs.map +1 -0
  481. package/dist/{zod-generator-C5JaV--i.mjs → zod-generator-CacrR-i8.mjs} +2 -2
  482. package/dist/{zod-generator-C5JaV--i.mjs.map → zod-generator-CacrR-i8.mjs.map} +1 -1
  483. package/package.json +7 -7
  484. package/src/api/handlers/content.ts +10 -7
  485. package/src/api/schemas/import.ts +19 -0
  486. package/src/astro/integration/runtime.ts +3 -6
  487. package/src/astro/middleware/auth.ts +9 -3
  488. package/src/astro/middleware/csp.ts +54 -3
  489. package/src/astro/routes/admin.astro +9 -0
  490. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +122 -98
  491. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +794 -44
  492. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +17 -15
  493. package/src/components/EmDashImage.astro +59 -23
  494. package/src/components/Image.astro +36 -35
  495. package/src/database/migrations/049_taxonomies_name_locale_index.ts +42 -0
  496. package/src/database/migrations/050_media_usage_index_status.ts +142 -0
  497. package/src/database/migrations/runner.ts +4 -0
  498. package/src/database/repositories/media-usage.ts +955 -59
  499. package/src/database/types.ts +22 -0
  500. package/src/emdash-runtime.ts +123 -3
  501. package/src/import/comments.ts +159 -0
  502. package/src/import/index.ts +8 -1
  503. package/src/import/settings.ts +65 -189
  504. package/src/import/sources/wordpress-plugin.ts +338 -42
  505. package/src/import/sources/wxr.ts +4 -1
  506. package/src/import/types.ts +12 -0
  507. package/src/import/utils.ts +207 -0
  508. package/src/import/wxr-taxonomies.ts +39 -0
  509. package/src/loader.ts +13 -2
  510. package/src/media/usage/content-fields.ts +119 -0
  511. package/src/media/usage/content-refresh.ts +524 -0
  512. package/src/media/usage/content-repair.ts +675 -0
  513. package/src/media/usage/content-snapshots.ts +439 -0
  514. package/src/media/usage/extractor.ts +4 -4
  515. package/src/media/usage/source-key.ts +22 -0
  516. package/src/menus/index.ts +17 -1
  517. package/src/object-cache/index.ts +82 -7
  518. package/src/plugins/context.ts +100 -71
  519. package/src/schema/registry.ts +240 -154
  520. package/src/seed/apply.ts +156 -99
  521. package/dist/api-80kycR9o.mjs.map +0 -1
  522. package/dist/apply-DSypk6Iu.mjs.map +0 -1
  523. package/dist/byline-fields-eyJMblSr.mjs.map +0 -1
  524. package/dist/context-CzUU_WzL.mjs.map +0 -1
  525. package/dist/import-Bb1T9WJS.mjs.map +0 -1
  526. package/dist/index-DGIjmUXQ.d.mts.map +0 -1
  527. package/dist/loader-4K37qA-y.mjs.map +0 -1
  528. package/dist/object-cache-CHbHv83-.mjs.map +0 -1
  529. package/dist/registry-DP-iEtdR.mjs.map +0 -1
  530. package/dist/runner-CFngB4WZ.mjs.map +0 -1
  531. package/dist/scheduled-publish-BMljtY5G.d.mts.map +0 -1
  532. package/dist/types-xIfVRNLp.d.mts.map +0 -1
  533. package/dist/utils-B7A57fm9.mjs.map +0 -1
  534. package/dist/version-CrqhKGWn.mjs +0 -7
  535. /package/dist/{api-tokens-B4BQybOp.mjs → api-tokens-DsvvCtdh.mjs} +0 -0
  536. /package/dist/{ssrf-CcX9zvMK.mjs → ssrf-CmuGgCq3.mjs} +0 -0
  537. /package/dist/{types-BvbeGEtr.mjs → types-DROWRxYV.mjs} +0 -0
@@ -7,26 +7,59 @@
7
7
  */
8
8
 
9
9
  import type { APIRoute } from "astro";
10
- import { ContentRepository, SchemaRegistry } from "emdash";
10
+ import {
11
+ ContentRepository,
12
+ SchemaRegistry,
13
+ type WxrCategory,
14
+ type WxrPost,
15
+ type WxrTag,
16
+ type WxrTerm,
17
+ } from "emdash";
18
+ import type { z } from "zod";
11
19
 
12
20
  import { requirePerm } from "#api/authorize.js";
13
21
  import { apiError, apiSuccess, handleError } from "#api/error.js";
22
+ import { handleTaxonomyCreate } from "#api/handlers/taxonomies.js";
14
23
  import { isParseError, parseBody } from "#api/parse.js";
15
24
  import { wpPluginExecuteBody } from "#api/schemas.js";
16
25
  import { BylineRepository } from "#db/repositories/byline.js";
26
+ import { importCommentsFromPlugin } from "#import/comments.js";
17
27
  import { getSource } from "#import/index.js";
28
+ import { importMenusFromPlugin } from "#import/menus.js";
29
+ import { importSiteSettings, parseSiteSettingsFromPlugin } from "#import/settings.js";
30
+ import {
31
+ fetchPluginComments,
32
+ fetchPluginCommentsPage,
33
+ fetchPluginContentPage,
34
+ fetchPluginMenus,
35
+ fetchPluginOptions,
36
+ fetchPluginTaxonomies,
37
+ } from "#import/sources/wordpress-plugin.js";
18
38
  import { resolveAndValidateExternalUrl, SsrfError } from "#import/ssrf.js";
19
39
  import type { ImportConfig, ImportResult, NormalizedItem } from "#import/types.js";
20
- import { resolveImportByline } from "#import/utils.js";
40
+ import { resolveImportByline, sanitizeFieldSlug } from "#import/utils.js";
41
+ import {
42
+ attachPostTaxonomies,
43
+ loadTaxonomyPlanFromDb,
44
+ preImportWxrTaxonomies,
45
+ type TaxonomyImportPlan,
46
+ } from "#import/wxr-taxonomies.js";
21
47
  import type { FieldType } from "#schema/types.js";
22
48
  import type { EmDashHandlers, EmDashManifest } from "#types";
23
49
  import { slugify } from "#utils/slugify.js";
24
50
 
51
+ import { importMediaWithProgress } from "../wordpress/media.js";
52
+
25
53
  export const prerender = false;
26
54
 
27
55
  export interface WpPluginImportConfig extends ImportConfig {
28
56
  /** Author mappings (WP author login -> EmDash user ID) */
29
57
  authorMappings?: Record<string, string | null>;
58
+ /** Wizard toggles. Absent means enabled (older admins don't send them). */
59
+ importMenus?: boolean;
60
+ importSiteTitle?: boolean;
61
+ importLogo?: boolean;
62
+ importSeo?: boolean;
30
63
  }
31
64
 
32
65
  export interface WpPluginImportResponse {
@@ -35,6 +68,71 @@ export interface WpPluginImportResponse {
35
68
  error?: { message: string };
36
69
  }
37
70
 
71
+ // =============================================================================
72
+ // Chunked mode (issue #475)
73
+ //
74
+ // A single-invocation import of a large site exceeds Cloudflare Worker
75
+ // resource limits (CPU time, subrequests). In chunked mode the admin drives
76
+ // the import as a loop of bounded requests: one WP content page per call,
77
+ // then paginated comments, then a small finalize step (menus + site
78
+ // identity). Cross-chunk state — the WP-ID -> EmDash-ID map, translation
79
+ // groups, comment threading roots — is accumulated by the client and sent
80
+ // back with each request, so the server stays stateless and an aborted
81
+ // import simply re-runs (skipExisting rebuilds the map while skipping work).
82
+ // =============================================================================
83
+
84
+ /** Posts per content chunk. 50 posts ≈ a few hundred D1 ops per invocation. */
85
+ const CONTENT_CHUNK_SIZE = 50;
86
+
87
+ interface ChunkCursor {
88
+ postTypeIndex: number;
89
+ page: number;
90
+ }
91
+
92
+ /** Per-chunk response payload: partial result + state the client must carry. */
93
+ interface ChunkResponse {
94
+ success: boolean;
95
+ result: ImportResult;
96
+ done: boolean;
97
+ cursor?: ChunkCursor;
98
+ chunk?: {
99
+ idMap?: Record<string, { id: string; collection: string }>;
100
+ translationGroups?: Record<string, string>;
101
+ commentRoots?: Record<string, string>;
102
+ };
103
+ }
104
+
105
+ function emptyImportResult(): ImportResult {
106
+ return { success: true, imported: 0, skipped: 0, errors: [], byCollection: {} };
107
+ }
108
+
109
+ function parseIdMap(idMap: Record<string, { id: string; collection: string }> | undefined): {
110
+ contentIdMap: Map<number, string>;
111
+ collectionByWpId: Map<number, string>;
112
+ } {
113
+ const contentIdMap = new Map<number, string>();
114
+ const collectionByWpId = new Map<number, string>();
115
+ for (const [key, value] of Object.entries(idMap ?? {})) {
116
+ const wpId = Number(key);
117
+ if (!Number.isFinite(wpId)) continue;
118
+ contentIdMap.set(wpId, value.id);
119
+ collectionByWpId.set(wpId, value.collection);
120
+ }
121
+ return { contentIdMap, collectionByWpId };
122
+ }
123
+
124
+ function serializeIdMap(
125
+ contentIdMap: Map<number, string>,
126
+ collectionByWpId: Map<number, string>,
127
+ ): Record<string, { id: string; collection: string }> {
128
+ const out: Record<string, { id: string; collection: string }> = {};
129
+ for (const [wpId, id] of contentIdMap) {
130
+ const collection = collectionByWpId.get(wpId);
131
+ if (collection) out[String(wpId)] = { id, collection };
132
+ }
133
+ return out;
134
+ }
135
+
38
136
  export const POST: APIRoute = async ({ request, locals }) => {
39
137
  const { emdash, user } = locals;
40
138
 
@@ -81,8 +179,27 @@ export const POST: APIRoute = async ({ request, locals }) => {
81
179
  console.log("[WP Plugin Import] Starting import for:", body.url);
82
180
  console.log("[WP Plugin Import] Post types:", postTypes);
83
181
 
182
+ // Chunked mode: one bounded unit of work per invocation (issue #475).
183
+ if (body.phase) {
184
+ const chunk = await runImportPhase(emdash, body, config, postTypes, emdashManifest);
185
+ return apiSuccess(chunk);
186
+ }
187
+
188
+ // Pre-create taxonomy defs (for custom CPT taxonomies) and terms so
189
+ // per-post assignments can attach.
190
+ // Non-fatal: a site whose /taxonomies call fails still gets content.
191
+ let taxonomyPlan: TaxonomyImportPlan | undefined;
192
+ let taxonomyDefsCreated: string[] = [];
193
+ try {
194
+ const built = await buildTaxonomyPlan(emdash, body.url, body.token, config);
195
+ taxonomyPlan = built.plan;
196
+ taxonomyDefsCreated = built.defsCreated;
197
+ } catch (e) {
198
+ console.warn("[WP Plugin Import] Taxonomy pre-import failed:", e);
199
+ }
200
+
84
201
  // Import content (including drafts since we have auth)
85
- const result = await importContent(
202
+ const { result, contentIdMap, collectionByWpId } = await importContent(
86
203
  source.fetchContent(
87
204
  { type: "url", url: body.url, token: body.token },
88
205
  { postTypes, includeDrafts: true },
@@ -90,8 +207,55 @@ export const POST: APIRoute = async ({ request, locals }) => {
90
207
  config,
91
208
  emdash,
92
209
  emdashManifest,
210
+ taxonomyPlan,
93
211
  );
94
212
 
213
+ // Import navigation menus, resolving item references through the
214
+ // WP-post-ID -> EmDash-ID map collected during the content pass.
215
+ if (config.importMenus !== false) {
216
+ await importMenusInto(result, emdash, body.url, body.token, contentIdMap);
217
+ }
218
+
219
+ // Import comments into EmDash's native comments table, preserving
220
+ // authors, dates, threading, and approval status.
221
+ // Non-fatal: older plugin versions have no /comments endpoint.
222
+ try {
223
+ const comments = await fetchPluginComments(body.url, body.token);
224
+ if (comments.length > 0) {
225
+ const commentsResult = await importCommentsFromPlugin(
226
+ comments,
227
+ emdash.db,
228
+ contentIdMap,
229
+ collectionByWpId,
230
+ );
231
+ result.comments = {
232
+ imported: commentsResult.imported,
233
+ skipped: commentsResult.skipped,
234
+ };
235
+ for (const commentError of commentsResult.errors) {
236
+ result.errors.push({
237
+ title: `Comment: ${commentError.comment}`,
238
+ error: commentError.error,
239
+ });
240
+ }
241
+ }
242
+ } catch (e) {
243
+ console.warn("[WP Plugin Import] Comment import failed:", e);
244
+ }
245
+
246
+ // Apply site identity (title, tagline, logo, favicon) from the source
247
+ // site's options, replacing the starter template's seed placeholders.
248
+ // Non-fatal: content is already imported at this point.
249
+ try {
250
+ result.siteSettings = await applySiteSettings(emdash, body.url, body.token, config);
251
+ } catch (e) {
252
+ console.warn("[WP Plugin Import] Site settings import failed:", e);
253
+ }
254
+
255
+ if (taxonomyDefsCreated.length > 0) {
256
+ result.taxonomiesCreated = taxonomyDefsCreated;
257
+ }
258
+
95
259
  console.log("[WP Plugin Import] Import result:", JSON.stringify(result, null, 2));
96
260
 
97
261
  return apiSuccess({
@@ -103,6 +267,213 @@ export const POST: APIRoute = async ({ request, locals }) => {
103
267
  }
104
268
  };
105
269
 
270
+ /**
271
+ * Import navigation menus into `result`, resolving item references through
272
+ * the WP-post-ID -> EmDash-ID map collected during the content pass.
273
+ * Non-fatal: older plugin versions have no /menus endpoint.
274
+ */
275
+ async function importMenusInto(
276
+ result: ImportResult,
277
+ emdash: EmDashHandlers,
278
+ url: string,
279
+ token: string,
280
+ contentIdMap: Map<number, string>,
281
+ ): Promise<void> {
282
+ try {
283
+ const menus = await fetchPluginMenus(url, token);
284
+ if (menus.length > 0) {
285
+ const menuResult = await importMenusFromPlugin(menus, emdash.db, contentIdMap);
286
+ result.menus = {
287
+ created: menuResult.menusCreated,
288
+ items: menuResult.itemsCreated,
289
+ };
290
+ for (const menuError of menuResult.errors) {
291
+ result.errors.push({ title: `Menu: ${menuError.menu}`, error: menuError.error });
292
+ }
293
+ }
294
+ } catch (e) {
295
+ console.warn("[WP Plugin Import] Menu import failed:", e);
296
+ }
297
+ }
298
+
299
+ type ExecuteBody = z.infer<typeof wpPluginExecuteBody>;
300
+
301
+ /** Dispatch one chunk of work for the requested phase. */
302
+ async function runImportPhase(
303
+ emdash: EmDashHandlers,
304
+ body: ExecuteBody,
305
+ config: WpPluginImportConfig,
306
+ postTypes: string[],
307
+ manifest: EmDashManifest,
308
+ ): Promise<ChunkResponse> {
309
+ switch (body.phase) {
310
+ case "content":
311
+ return runContentChunk(emdash, body, config, postTypes, manifest);
312
+ case "comments":
313
+ return runCommentsChunk(emdash, body);
314
+ case "finalize":
315
+ return runFinalizePhase(emdash, body, config);
316
+ case undefined:
317
+ throw new Error("runImportPhase called without a phase");
318
+ default:
319
+ body.phase satisfies never;
320
+ throw new Error("Unknown import phase");
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Import one page of one post type. The first chunk additionally runs the
326
+ * taxonomy setup (def + term creation); later chunks only reload the
327
+ * lookup maps from the database.
328
+ */
329
+ async function runContentChunk(
330
+ emdash: EmDashHandlers,
331
+ body: ExecuteBody,
332
+ config: WpPluginImportConfig,
333
+ postTypes: string[],
334
+ manifest: EmDashManifest,
335
+ ): Promise<ChunkResponse> {
336
+ const cursor = body.cursor ?? { postTypeIndex: 0, page: 1 };
337
+ const postType = postTypes[cursor.postTypeIndex];
338
+ if (!postType) {
339
+ return { success: true, result: emptyImportResult(), done: true };
340
+ }
341
+
342
+ const isFirstChunk = cursor.postTypeIndex === 0 && cursor.page === 1;
343
+ let taxonomyPlan: TaxonomyImportPlan | undefined;
344
+ let taxonomyDefsCreated: string[] = [];
345
+ try {
346
+ if (isFirstChunk) {
347
+ const built = await buildTaxonomyPlan(emdash, body.url, body.token, config);
348
+ taxonomyPlan = built.plan;
349
+ taxonomyDefsCreated = built.defsCreated;
350
+ } else {
351
+ taxonomyPlan = await loadTaxonomyPlanFromDb(emdash.db);
352
+ }
353
+ } catch (e) {
354
+ console.warn("[WP Plugin Import] Taxonomy pre-import failed:", e);
355
+ }
356
+
357
+ const page = await fetchPluginContentPage({
358
+ siteUrl: body.url,
359
+ token: body.token,
360
+ postType,
361
+ page: cursor.page,
362
+ perPage: CONTENT_CHUNK_SIZE,
363
+ includeDrafts: true,
364
+ });
365
+
366
+ // Seed translation-group state from earlier chunks so a translation in
367
+ // this page links to its sibling imported three chunks ago.
368
+ const translationGroupMap = new Map(Object.entries(body.translationGroups ?? {}));
369
+
370
+ const { result, contentIdMap, collectionByWpId } = await importContent(
371
+ page.items,
372
+ config,
373
+ emdash,
374
+ manifest,
375
+ taxonomyPlan,
376
+ translationGroupMap,
377
+ );
378
+
379
+ if (taxonomyDefsCreated.length > 0) {
380
+ result.taxonomiesCreated = taxonomyDefsCreated;
381
+ }
382
+
383
+ // Advance: next page of this post type, first page of the next one,
384
+ // or done. An empty last page (totalPages can shrink while paginating
385
+ // a live site) still terminates because page >= totalPages.
386
+ let next: ChunkCursor | undefined;
387
+ if (cursor.page < page.totalPages) {
388
+ next = { postTypeIndex: cursor.postTypeIndex, page: cursor.page + 1 };
389
+ } else if (cursor.postTypeIndex + 1 < postTypes.length) {
390
+ next = { postTypeIndex: cursor.postTypeIndex + 1, page: 1 };
391
+ }
392
+
393
+ return {
394
+ success: true,
395
+ result,
396
+ done: next === undefined,
397
+ cursor: next,
398
+ chunk: {
399
+ idMap: serializeIdMap(contentIdMap, collectionByWpId),
400
+ translationGroups: Object.fromEntries(translationGroupMap),
401
+ },
402
+ };
403
+ }
404
+
405
+ /**
406
+ * Import one page of comments (500 per page, ordered by WP comment ID so
407
+ * parents precede children across pages). Requires the accumulated idMap
408
+ * from the content phase; threading roots accumulate in `commentRoots`.
409
+ */
410
+ async function runCommentsChunk(emdash: EmDashHandlers, body: ExecuteBody): Promise<ChunkResponse> {
411
+ const page = body.cursor?.page ?? 1;
412
+ const result = emptyImportResult();
413
+
414
+ const { contentIdMap, collectionByWpId } = parseIdMap(body.idMap);
415
+ const rootIds = new Map<number, string>();
416
+ for (const [key, value] of Object.entries(body.commentRoots ?? {})) {
417
+ const wpId = Number(key);
418
+ if (Number.isFinite(wpId)) rootIds.set(wpId, value);
419
+ }
420
+
421
+ const { items, totalPages } = await fetchPluginCommentsPage(body.url, body.token, page);
422
+
423
+ if (items.length > 0) {
424
+ const commentsResult = await importCommentsFromPlugin(
425
+ items,
426
+ emdash.db,
427
+ contentIdMap,
428
+ collectionByWpId,
429
+ rootIds,
430
+ );
431
+ result.comments = {
432
+ imported: commentsResult.imported,
433
+ skipped: commentsResult.skipped,
434
+ };
435
+ for (const commentError of commentsResult.errors) {
436
+ result.errors.push({ title: `Comment: ${commentError.comment}`, error: commentError.error });
437
+ }
438
+ result.success = result.errors.length === 0;
439
+ }
440
+
441
+ const done = page >= totalPages;
442
+ const commentRoots: Record<string, string> = {};
443
+ for (const [wpId, id] of rootIds) commentRoots[String(wpId)] = id;
444
+
445
+ return {
446
+ success: true,
447
+ result,
448
+ done,
449
+ cursor: done ? undefined : { postTypeIndex: 0, page: page + 1 },
450
+ chunk: { commentRoots },
451
+ };
452
+ }
453
+
454
+ /** Menus + site identity — small, runs as a single closing chunk. */
455
+ async function runFinalizePhase(
456
+ emdash: EmDashHandlers,
457
+ body: ExecuteBody,
458
+ config: WpPluginImportConfig,
459
+ ): Promise<ChunkResponse> {
460
+ const result = emptyImportResult();
461
+ const { contentIdMap } = parseIdMap(body.idMap);
462
+
463
+ if (config.importMenus !== false) {
464
+ await importMenusInto(result, emdash, body.url, body.token, contentIdMap);
465
+ }
466
+
467
+ try {
468
+ result.siteSettings = await applySiteSettings(emdash, body.url, body.token, config);
469
+ } catch (e) {
470
+ console.warn("[WP Plugin Import] Site settings import failed:", e);
471
+ }
472
+
473
+ result.success = result.errors.length === 0;
474
+ return { success: true, result, done: true };
475
+ }
476
+
106
477
  /** Fields that should be auto-created if they don't exist */
107
478
  const IMPORT_FIELDS: Array<{
108
479
  slug: string;
@@ -134,14 +505,285 @@ const IMPORT_FIELDS: Array<{
134
505
  type: "image",
135
506
  check: (item) => !!item.featuredImage,
136
507
  },
508
+ {
509
+ slug: "seo_title",
510
+ label: "SEO Title",
511
+ type: "string",
512
+ check: (item) => !!extractSeo(item).title,
513
+ },
514
+ {
515
+ slug: "seo_description",
516
+ label: "SEO Description",
517
+ type: "text",
518
+ check: (item) => !!extractSeo(item).description,
519
+ },
137
520
  ];
138
521
 
139
- async function importContent(
140
- items: AsyncGenerator<NormalizedItem>,
522
+ const SEO_FIELD_SLUGS = new Set(["seo_title", "seo_description"]);
523
+
524
+ /**
525
+ * Coerce a WordPress meta value to an EmDash field type. WP postmeta is
526
+ * stringly typed and inconsistent across posts (the same key can hold
527
+ * "5", 5, "", or false). Returns `undefined` when the value can't
528
+ * reasonably represent the target type — the caller drops it.
529
+ */
530
+ export function coerceToFieldType(value: unknown, fieldType: string): unknown {
531
+ switch (fieldType) {
532
+ case "integer": {
533
+ const n = typeof value === "number" ? value : Number(value);
534
+ return Number.isInteger(n) ? n : undefined;
535
+ }
536
+ case "number": {
537
+ const n = typeof value === "number" ? value : Number(value);
538
+ return Number.isFinite(n) ? n : undefined;
539
+ }
540
+ case "boolean": {
541
+ if (typeof value === "boolean") return value;
542
+ if (value === 1 || value === "1" || value === "true" || value === "yes") return true;
543
+ if (value === 0 || value === "0" || value === "false" || value === "no") return false;
544
+ return undefined;
545
+ }
546
+ case "datetime": {
547
+ if (typeof value !== "string" && typeof value !== "number") return undefined;
548
+ const d = new Date(value);
549
+ return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
550
+ }
551
+ case "json":
552
+ // Anything serializes; objects/arrays pass through as-is
553
+ return value;
554
+ case "string":
555
+ case "text":
556
+ case "url":
557
+ case "select":
558
+ case "slug":
559
+ case "reference":
560
+ if (typeof value === "string") return value;
561
+ if (typeof value === "number") return String(value);
562
+ return undefined;
563
+ default:
564
+ // Complex types (image, portableText, repeater, multiSelect, file):
565
+ // raw meta can't be coerced reliably -- pass through only if it
566
+ // already has a non-primitive shape.
567
+ return typeof value === "object" ? value : undefined;
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Pull the per-post SEO title/description out of the Yoast / Rank Math
573
+ * blobs the plugin source stashes in `item.meta`. Empty strings mean "not
574
+ * overridden for this post" (the plugin exports the raw meta values) and
575
+ * are treated as absent.
576
+ */
577
+ function pickSeoValue(blob: unknown, key: string): string | undefined {
578
+ if (typeof blob !== "object" || blob === null) return undefined;
579
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowed to non-null object above
580
+ const value = (blob as Record<string, unknown>)[key];
581
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
582
+ }
583
+
584
+ function extractSeo(item: NormalizedItem): { title?: string; description?: string } {
585
+ const yoast = item.meta?._yoast;
586
+ const rankmath = item.meta?._rankmath;
587
+ return {
588
+ title: pickSeoValue(yoast, "title") ?? pickSeoValue(rankmath, "title"),
589
+ description: pickSeoValue(yoast, "description") ?? pickSeoValue(rankmath, "description"),
590
+ };
591
+ }
592
+
593
+ /**
594
+ * Fetch the site's taxonomies from the plugin API and pre-create all terms,
595
+ * reusing the WXR taxonomy machinery (same def-lookup, idempotency, and
596
+ * collection-filter semantics).
597
+ */
598
+ /** WP built-in taxonomies that either map to seeded defs or are not content taxonomies. */
599
+ const BUILTIN_TAXONOMIES = new Set(["category", "post_tag", "nav_menu", "post_format"]);
600
+
601
+ /** Mirrors NAME_PATTERN in the taxonomy handler -- names that fail stay in missingTaxonomies. */
602
+ const TAXONOMY_NAME_PATTERN = /^[a-z][a-z0-9_]*$/;
603
+
604
+ /**
605
+ * Create EmDash taxonomy defs for custom WP taxonomies (e.g. CPT taxonomies
606
+ * like `company` or `plattform`) that don't exist yet, scoped to the
607
+ * collections the enabled post-type mappings target. Returns the names of
608
+ * the defs created. Runs before term pre-import so the terms and per-post
609
+ * assignments flow through the existing machinery instead of being dropped
610
+ * as `missingTaxonomies`.
611
+ */
612
+ export async function ensureCustomTaxonomyDefs(
613
+ db: EmDashHandlers["db"],
614
+ taxonomies: Awaited<ReturnType<typeof fetchPluginTaxonomies>>,
615
+ config: WpPluginImportConfig,
616
+ ): Promise<string[]> {
617
+ const created: string[] = [];
618
+ for (const taxonomy of taxonomies) {
619
+ if (BUILTIN_TAXONOMIES.has(taxonomy.name)) continue;
620
+ if (taxonomy.terms.length === 0) continue;
621
+ if (!TAXONOMY_NAME_PATTERN.test(taxonomy.name)) continue;
622
+
623
+ // handleTaxonomyCreate's duplicate guard is locale-scoped; check
624
+ // name-wide existence ourselves to stay idempotent on re-runs.
625
+ const existing = await db
626
+ .selectFrom("_emdash_taxonomy_defs")
627
+ .select("id")
628
+ .where("name", "=", taxonomy.name)
629
+ .executeTakeFirst();
630
+ if (existing) continue;
631
+
632
+ // Scope the def to the collections the WP post types map onto.
633
+ // Older plugin versions don't send post_types -> empty list = no
634
+ // collection filter, which the term machinery treats as "any".
635
+ const collections = (taxonomy.post_types ?? [])
636
+ .map((postType) => config.postTypeMappings[postType])
637
+ .filter((mapping) => mapping?.enabled)
638
+ .map((mapping) => mapping.collection);
639
+
640
+ const result = await handleTaxonomyCreate(db, {
641
+ name: taxonomy.name,
642
+ label: taxonomy.label,
643
+ labelSingular: taxonomy.label_singular,
644
+ hierarchical: taxonomy.hierarchical,
645
+ collections: [...new Set(collections)],
646
+ });
647
+ if (result.success) {
648
+ created.push(taxonomy.name);
649
+ } else {
650
+ console.warn(
651
+ `[WP Plugin Import] Could not create taxonomy '${taxonomy.name}':`,
652
+ result.error.message,
653
+ );
654
+ }
655
+ }
656
+ return created;
657
+ }
658
+
659
+ async function buildTaxonomyPlan(
660
+ emdash: EmDashHandlers,
661
+ url: string,
662
+ token: string,
663
+ config: WpPluginImportConfig,
664
+ ): Promise<{ plan: TaxonomyImportPlan; defsCreated: string[] }> {
665
+ const taxonomies = await fetchPluginTaxonomies(url, token);
666
+
667
+ const defsCreated = await ensureCustomTaxonomyDefs(emdash.db, taxonomies, config);
668
+
669
+ const categories: WxrCategory[] = [];
670
+ const tags: WxrTag[] = [];
671
+ const terms: WxrTerm[] = [];
672
+
673
+ for (const taxonomy of taxonomies) {
674
+ for (const term of taxonomy.terms) {
675
+ if (taxonomy.name === "category") {
676
+ categories.push({ nicename: term.slug, name: term.name, description: term.description });
677
+ } else if (taxonomy.name === "post_tag") {
678
+ tags.push({ slug: term.slug, name: term.name, description: term.description });
679
+ } else if (taxonomy.name !== "nav_menu" && taxonomy.name !== "post_format") {
680
+ terms.push({
681
+ id: term.id,
682
+ taxonomy: taxonomy.name,
683
+ slug: term.slug,
684
+ name: term.name,
685
+ description: term.description,
686
+ });
687
+ }
688
+ }
689
+ }
690
+
691
+ const plan = await preImportWxrTaxonomies(emdash.db, [], categories, tags, terms, undefined);
692
+ return { plan, defsCreated };
693
+ }
694
+
695
+ /**
696
+ * Fetch the source site's options and apply its identity (title, tagline,
697
+ * logo, favicon) as EmDash site settings, overwriting seed placeholders.
698
+ * Logo/favicon files are side-loaded into media storage first; the later
699
+ * full media pass dedupes them by content hash.
700
+ *
701
+ * Returns the list of applied setting keys.
702
+ */
703
+ async function applySiteSettings(
704
+ emdash: EmDashHandlers,
705
+ url: string,
706
+ token: string,
707
+ config: WpPluginImportConfig,
708
+ ): Promise<string[]> {
709
+ const wantTitle = config.importSiteTitle !== false;
710
+ const wantLogo = config.importLogo !== false;
711
+ if (!wantTitle && !wantLogo) return [];
712
+
713
+ const options = await fetchPluginOptions(url, token);
714
+ const parsed = parseSiteSettingsFromPlugin(options);
715
+ if (!wantTitle) {
716
+ delete parsed.title;
717
+ delete parsed.tagline;
718
+ }
719
+ if (!wantLogo) {
720
+ delete parsed.logo;
721
+ delete parsed.favicon;
722
+ }
723
+
724
+ const media: { logoMediaId?: string; faviconMediaId?: string } = {};
725
+ if (emdash.storage && (parsed.logo?.url || parsed.favicon?.url)) {
726
+ const attachments = [];
727
+ if (parsed.logo?.url) {
728
+ attachments.push({ id: parsed.logo.id, url: parsed.logo.url });
729
+ }
730
+ if (parsed.favicon?.url && parsed.favicon.url !== parsed.logo?.url) {
731
+ attachments.push({ id: parsed.favicon.id, url: parsed.favicon.url });
732
+ }
733
+ const mediaResult = await importMediaWithProgress(
734
+ attachments,
735
+ emdash.db,
736
+ emdash.storage,
737
+ () => {},
738
+ );
739
+ for (const item of mediaResult.imported) {
740
+ if (item.originalUrl === parsed.logo?.url) {
741
+ media.logoMediaId = item.mediaId;
742
+ }
743
+ if (item.originalUrl === parsed.favicon?.url) {
744
+ media.faviconMediaId = item.mediaId;
745
+ }
746
+ }
747
+ }
748
+
749
+ const settingsResult = await importSiteSettings(parsed, emdash.db, true, media);
750
+ for (const settingError of settingsResult.errors) {
751
+ console.warn(
752
+ `[WP Plugin Import] Site setting "${settingError.setting}" failed:`,
753
+ settingError.error,
754
+ );
755
+ }
756
+ return settingsResult.applied;
757
+ }
758
+
759
+ /**
760
+ * Adapt a NormalizedItem's taxonomy assignments to the WxrPost shape the
761
+ * shared attach helper consumes.
762
+ */
763
+ function toWxrAssignments(item: NormalizedItem): WxrPost {
764
+ return {
765
+ categories: item.categories ?? [],
766
+ tags: item.tags ?? [],
767
+ customTaxonomies: item.customTaxonomies
768
+ ? new Map(Object.entries(item.customTaxonomies))
769
+ : undefined,
770
+ meta: new Map(),
771
+ };
772
+ }
773
+
774
+ /** Exported for tests (field auto-creation regression coverage). */
775
+ export async function importContent(
776
+ items: AsyncIterable<NormalizedItem> | Iterable<NormalizedItem>,
141
777
  config: WpPluginImportConfig,
142
778
  emdash: EmDashHandlers,
143
779
  manifest: EmDashManifest,
144
- ): Promise<ImportResult> {
780
+ taxonomyPlan: TaxonomyImportPlan | undefined,
781
+ seedTranslationGroups?: Map<string, string>,
782
+ ): Promise<{
783
+ result: ImportResult;
784
+ contentIdMap: Map<number, string>;
785
+ collectionByWpId: Map<number, string>;
786
+ }> {
145
787
  const result: ImportResult = {
146
788
  success: true,
147
789
  imported: 0,
@@ -150,19 +792,32 @@ async function importContent(
150
792
  byCollection: {},
151
793
  };
152
794
 
795
+ // WP post ID -> EmDash content ID, used to resolve menu item references
796
+ const contentIdMap = new Map<number, string>();
797
+
798
+ // WP post ID -> EmDash collection slug, used by the comment import
799
+ const collectionByWpId = new Map<number, string>();
800
+
153
801
  // Create content repository for checking existing items
154
802
  const contentRepo = new ContentRepository(emdash.db);
155
803
  const bylineRepo = new BylineRepository(emdash.db);
156
804
  const bylineCache = new Map<string, string>();
157
805
  const schemaRegistry = new SchemaRegistry(emdash.db);
158
806
 
159
- // Track which collections have had fields ensured
160
- const ensuredCollections = new Set<string>();
807
+ // Track which (collection, field) pairs have been ensured. Keyed per
808
+ // field, not per collection: a field like seo_title may first be
809
+ // needed by the 30th item, and gating on the first item would skip it.
810
+ const ensuredFields = new Set<string>();
811
+
812
+ // Field slug -> type per collection, for mapping custom meta/ACF onto
813
+ // real fields (and coercing values to the field's type)
814
+ const fieldTypesByCollection = new Map<string, Map<string, string>>();
161
815
 
162
816
  // Track source translationGroup -> EmDash item ID for translation linking.
163
817
  // Maps source-side translation group ID to the EmDash ID of the first item
164
- // imported for that group (the default-locale item).
165
- const translationGroupMap = new Map<string, string>();
818
+ // imported for that group (the default-locale item). The chunked import
819
+ // seeds this from earlier chunks so groups can span page boundaries.
820
+ const translationGroupMap = seedTranslationGroups ?? new Map<string, string>();
166
821
 
167
822
  for await (const item of items) {
168
823
  console.log("[WP Plugin Import] Processing item:", {
@@ -196,33 +851,51 @@ async function importContent(
196
851
  }
197
852
 
198
853
  try {
199
- // Ensure required fields exist in the collection schema (once per collection)
200
- if (!ensuredCollections.has(collection)) {
201
- for (const field of IMPORT_FIELDS) {
202
- if (field.check(item)) {
203
- const existingField = await schemaRegistry.getField(collection, field.slug);
204
- if (!existingField) {
205
- console.log(
206
- `[WP Plugin Import] Creating missing field "${field.slug}" in collection "${collection}"`,
207
- );
208
- try {
209
- await schemaRegistry.createField(collection, {
210
- slug: field.slug,
211
- label: field.label,
212
- type: field.type,
213
- required: false,
214
- });
215
- } catch (e) {
216
- // Field might already exist from concurrent creation
217
- console.log(
218
- `[WP Plugin Import] Field "${field.slug}" creation skipped:`,
219
- e instanceof Error ? e.message : e,
220
- );
221
- }
222
- }
854
+ // Ensure required fields exist in the collection schema. Checked
855
+ // per field and item (not once per collection): whether a field
856
+ // is needed depends on the item — the first post may have no SEO
857
+ // override while a later one does.
858
+ for (const field of IMPORT_FIELDS) {
859
+ if (config.importSeo === false && SEO_FIELD_SLUGS.has(field.slug)) continue;
860
+ const ensureKey = `${collection}:${field.slug}`;
861
+ if (ensuredFields.has(ensureKey) || !field.check(item)) continue;
862
+ ensuredFields.add(ensureKey);
863
+ const existingField = await schemaRegistry.getField(collection, field.slug);
864
+ if (!existingField) {
865
+ console.log(
866
+ `[WP Plugin Import] Creating missing field "${field.slug}" in collection "${collection}"`,
867
+ );
868
+ try {
869
+ await schemaRegistry.createField(collection, {
870
+ slug: field.slug,
871
+ label: field.label,
872
+ type: field.type,
873
+ required: false,
874
+ });
875
+ fieldTypesByCollection.get(collection)?.set(field.slug, field.type);
876
+ } catch (e) {
877
+ // Field might already exist from concurrent creation
878
+ console.log(
879
+ `[WP Plugin Import] Field "${field.slug}" creation skipped:`,
880
+ e instanceof Error ? e.message : e,
881
+ );
223
882
  }
224
883
  }
225
- ensuredCollections.add(collection);
884
+ }
885
+
886
+ // Load the collection's field types once, so custom meta / ACF
887
+ // values can land in matching schema fields (created by the
888
+ // prepare step or already present).
889
+ let fieldTypes = fieldTypesByCollection.get(collection);
890
+ if (!fieldTypes) {
891
+ fieldTypes = new Map<string, string>();
892
+ const collectionDef = await schemaRegistry.getCollection(collection);
893
+ if (collectionDef) {
894
+ for (const field of await schemaRegistry.listFields(collectionDef.id)) {
895
+ fieldTypes.set(field.slug, field.type);
896
+ }
897
+ }
898
+ fieldTypesByCollection.set(collection, fieldTypes);
226
899
  }
227
900
 
228
901
  // Generate slug from item slug or title
@@ -236,6 +909,12 @@ async function importContent(
236
909
  if (item.translationGroup) {
237
910
  translationGroupMap.set(item.translationGroup, existing.id);
238
911
  }
912
+ // Menus and comments may reference this post even when we skip it
913
+ const wpId = Number(item.sourceId);
914
+ if (Number.isFinite(wpId)) {
915
+ contentIdMap.set(wpId, existing.id);
916
+ collectionByWpId.set(wpId, collection);
917
+ }
239
918
  result.skipped++;
240
919
  continue;
241
920
  }
@@ -259,8 +938,49 @@ async function importContent(
259
938
  console.log("[WP Plugin Import] Adding featured_image:", item.featuredImage);
260
939
  }
261
940
 
262
- // Note: ACF/Yoast/RankMath fields are not added automatically
263
- // They would need matching fields in the EmDash schema
941
+ // Per-post SEO overrides from Yoast / Rank Math
942
+ if (config.importSeo !== false) {
943
+ const seo = extractSeo(item);
944
+ if (seo.title) {
945
+ data.seo_title = seo.title;
946
+ }
947
+ if (seo.description) {
948
+ data.seo_description = seo.description;
949
+ }
950
+ }
951
+
952
+ // Map ACF values and custom meta onto schema fields with the same
953
+ // (sanitized) slug — the same sanitization the analysis applied
954
+ // when suggesting the fields, so keys like `event-date` land in
955
+ // the `event_date` field the prepare step created. Values without
956
+ // a matching field are dropped -- the user controls the schema,
957
+ // we don't invent fields per meta key. Values are coerced to the
958
+ // field's type (the analysis inferred it from ONE sample; real
959
+ // values vary) and dropped when incoercible, so one odd meta
960
+ // value can't fail the whole item.
961
+ const assignMetaValue = (key: string, value: unknown) => {
962
+ if (value === null || value === "") return;
963
+ const fieldSlug = sanitizeFieldSlug(key);
964
+ const fieldType = fieldTypes.get(fieldSlug);
965
+ if (!fieldType || fieldSlug in data) return;
966
+ const coerced = coerceToFieldType(value, fieldType);
967
+ if (coerced !== undefined) {
968
+ data[fieldSlug] = coerced;
969
+ }
970
+ };
971
+ const acf = item.meta?._acf;
972
+ if (typeof acf === "object" && acf !== null) {
973
+ for (const [key, value] of Object.entries(acf)) {
974
+ assignMetaValue(key, value);
975
+ }
976
+ }
977
+ if (item.meta) {
978
+ for (const [key, value] of Object.entries(item.meta)) {
979
+ // Underscore keys are WP-internal or handled above (_acf/_yoast/_rankmath)
980
+ if (key.startsWith("_")) continue;
981
+ assignMetaValue(key, value);
982
+ }
983
+ }
264
984
 
265
985
  // Resolve author ID from mappings
266
986
  let authorId: string | undefined;
@@ -314,13 +1034,39 @@ async function importContent(
314
1034
  result.imported++;
315
1035
  result.byCollection[collection] = (result.byCollection[collection] || 0) + 1;
316
1036
 
317
- // Track translation group: first item in a group establishes the mapping
318
- if (item.translationGroup && !translationGroupMap.has(item.translationGroup)) {
319
- // eslint-disable-next-line typescript/no-unsafe-type-assertion -- handler success data includes id
320
- const createdData = createResult.data as { id?: string } | undefined;
321
- if (createdData?.id) {
322
- translationGroupMap.set(item.translationGroup, createdData.id);
1037
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- create handler returns { item, _rev }
1038
+ const createdData = createResult.data as { item?: { id?: string } } | undefined;
1039
+ const createdId = createdData?.item?.id;
1040
+
1041
+ if (createdId) {
1042
+ const wpId = Number(item.sourceId);
1043
+ if (Number.isFinite(wpId)) {
1044
+ contentIdMap.set(wpId, createdId);
1045
+ collectionByWpId.set(wpId, collection);
323
1046
  }
1047
+
1048
+ // Attach category/tag/custom-taxonomy assignments
1049
+ if (taxonomyPlan) {
1050
+ try {
1051
+ const attached = await attachPostTaxonomies(
1052
+ emdash.db,
1053
+ collection,
1054
+ createdId,
1055
+ toWxrAssignments(item),
1056
+ taxonomyPlan,
1057
+ );
1058
+ if (attached > 0) {
1059
+ result.taxonomyAssignments = (result.taxonomyAssignments ?? 0) + attached;
1060
+ }
1061
+ } catch (e) {
1062
+ console.warn(`[WP Plugin Import] Taxonomy attach failed for "${slug}":`, e);
1063
+ }
1064
+ }
1065
+ }
1066
+
1067
+ // Track translation group: first item in a group establishes the mapping
1068
+ if (item.translationGroup && !translationGroupMap.has(item.translationGroup) && createdId) {
1069
+ translationGroupMap.set(item.translationGroup, createdId);
324
1070
  }
325
1071
  } else {
326
1072
  result.errors.push({
@@ -340,8 +1086,12 @@ async function importContent(
340
1086
  }
341
1087
  }
342
1088
 
1089
+ if (taxonomyPlan && taxonomyPlan.missingTaxonomies.length > 0) {
1090
+ result.missingTaxonomies = taxonomyPlan.missingTaxonomies;
1091
+ }
1092
+
343
1093
  result.success = result.errors.length === 0;
344
- return result;
1094
+ return { result, contentIdMap, collectionByWpId };
345
1095
  }
346
1096
 
347
1097
  function mapStatus(wpStatus: string | undefined): string {