emdash 0.36.0 → 0.38.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 (991) hide show
  1. package/dist/{activation-K0kGBWuJ.mjs → activation-DyNTe_Tl.mjs} +3 -3
  2. package/dist/{activation-K0kGBWuJ.mjs.map → activation-DyNTe_Tl.mjs.map} +1 -1
  3. package/dist/{adapters-CzL3H752.d.mts → adapters-DCEgOBXi.d.mts} +4 -2
  4. package/dist/{adapters-CzL3H752.d.mts.map → adapters-DCEgOBXi.d.mts.map} +1 -1
  5. package/dist/{allowed-origins-maylGaHv.mjs → allowed-origins-GFUVavRj.mjs} +2 -2
  6. package/dist/{allowed-origins-maylGaHv.mjs.map → allowed-origins-GFUVavRj.mjs.map} +1 -1
  7. package/dist/api/route-utils.d.mts +3 -3
  8. package/dist/api/route-utils.mjs +25 -24
  9. package/dist/api/route-utils.mjs.map +1 -1
  10. package/dist/api/schemas/index.d.mts +2 -2
  11. package/dist/api/schemas/index.mjs +5 -5
  12. package/dist/api-BvxJmnFY.mjs +10094 -0
  13. package/dist/api-BvxJmnFY.mjs.map +1 -0
  14. package/dist/{api-tokens-9YrrCoE6.mjs → api-tokens-xpGqMHrC.mjs} +2 -2
  15. package/dist/{api-tokens-9YrrCoE6.mjs.map → api-tokens-xpGqMHrC.mjs.map} +1 -1
  16. package/dist/{apply-B_VMpJ2P.mjs → apply-DLmM3R7f.mjs} +26 -22
  17. package/dist/apply-DLmM3R7f.mjs.map +1 -0
  18. package/dist/artifact-fetch-P2JXVwOq.mjs +972 -0
  19. package/dist/artifact-fetch-P2JXVwOq.mjs.map +1 -0
  20. package/dist/astro/image-endpoint.mjs +2 -2
  21. package/dist/astro/image-endpoint.mjs.map +1 -1
  22. package/dist/astro/index.d.mts +12 -12
  23. package/dist/astro/index.d.mts.map +1 -1
  24. package/dist/astro/index.mjs +138 -39
  25. package/dist/astro/index.mjs.map +1 -1
  26. package/dist/astro/middleware/auth.d.mts +10 -10
  27. package/dist/astro/middleware/auth.mjs +6 -6
  28. package/dist/astro/middleware/media-usage-write-fence.mjs +3 -3
  29. package/dist/astro/middleware/redirect.mjs +7 -5
  30. package/dist/astro/middleware/redirect.mjs.map +1 -1
  31. package/dist/astro/middleware/request-context.mjs +3 -3
  32. package/dist/astro/middleware/setup.mjs +1 -1
  33. package/dist/astro/middleware.d.mts +10 -10
  34. package/dist/astro/middleware.d.mts.map +1 -1
  35. package/dist/astro/middleware.mjs +223 -232
  36. package/dist/astro/middleware.mjs.map +1 -1
  37. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  38. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  39. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
  40. package/dist/astro/routes/api/admin/api-tokens/index.d.mts.map +1 -1
  41. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  42. package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
  43. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  44. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -9
  45. package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -9
  46. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -9
  47. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +17 -17
  48. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -17
  49. package/dist/astro/routes/api/admin/bylines/index.mjs +17 -17
  50. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +13 -13
  51. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  52. package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
  53. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  54. package/dist/astro/routes/api/admin/comments/index.mjs +10 -10
  55. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  56. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  57. package/dist/astro/routes/api/admin/media-usage/activation.mjs +13 -13
  58. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +15 -15
  59. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +15 -15
  60. package/dist/astro/routes/api/admin/media-usage/progress.mjs +14 -14
  61. package/dist/astro/routes/api/admin/media-usage/repair.mjs +17 -17
  62. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +15 -15
  63. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +15 -15
  64. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  65. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs.map +1 -1
  66. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  67. package/dist/astro/routes/api/admin/oauth-clients/index.mjs.map +1 -1
  68. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +59 -56
  69. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +59 -56
  71. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  72. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +57 -54
  73. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  74. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  75. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +57 -54
  76. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +58 -55
  78. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +58 -55
  80. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  81. package/dist/astro/routes/api/admin/plugins/index.mjs +57 -54
  82. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  84. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +57 -54
  85. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +58 -55
  87. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +57 -54
  89. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +58 -55
  91. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  93. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +64 -57
  94. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +18 -16
  96. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  97. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +65 -58
  99. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts +8 -0
  101. package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts.map +1 -0
  102. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +96 -0
  103. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -0
  104. package/dist/astro/routes/api/admin/plugins/updates.mjs +57 -54
  105. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  106. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +57 -54
  107. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  108. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  109. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +57 -54
  110. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  111. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +1 -1
  112. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  113. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  114. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  115. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  116. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  117. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  118. package/dist/astro/routes/api/auth/invite/complete.mjs +11 -11
  119. package/dist/astro/routes/api/auth/invite/index.mjs +8 -8
  120. package/dist/astro/routes/api/auth/invite/register-options.mjs +10 -10
  121. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  122. package/dist/astro/routes/api/auth/magic-link/send.mjs +10 -10
  123. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  124. package/dist/astro/routes/api/auth/me.mjs +7 -7
  125. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  126. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  127. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  128. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  129. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  130. package/dist/astro/routes/api/auth/passkey/options.mjs +12 -12
  131. package/dist/astro/routes/api/auth/passkey/register/options.mjs +10 -10
  132. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +11 -11
  133. package/dist/astro/routes/api/auth/passkey/verify.mjs +11 -11
  134. package/dist/astro/routes/api/auth/signup/complete.mjs +11 -11
  135. package/dist/astro/routes/api/auth/signup/request.mjs +10 -10
  136. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  137. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -14
  138. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +13 -13
  139. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  140. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts.map +1 -1
  141. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +19 -5
  142. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  143. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  144. package/dist/astro/routes/api/content/_collection_/_id_/lock.d.mts +10 -0
  145. package/dist/astro/routes/api/content/_collection_/_id_/lock.d.mts.map +1 -0
  146. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +66 -0
  147. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs.map +1 -0
  148. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  149. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +11 -11
  150. package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts.map +1 -1
  151. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +18 -10
  152. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  153. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +20 -18
  154. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  155. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +20 -18
  156. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  157. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  158. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  159. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
  160. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +15 -8
  161. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  162. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +16 -15
  163. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  164. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  165. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts.map +1 -1
  166. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +19 -5
  167. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  168. package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
  169. package/dist/astro/routes/api/content/_collection_/_id_.mjs +23 -11
  170. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  171. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  172. package/dist/astro/routes/api/content/_collection_/index.mjs +14 -9
  173. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  174. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  175. package/dist/astro/routes/api/dashboard.mjs +8 -8
  176. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  177. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  178. package/dist/astro/routes/api/import/probe.mjs +18 -18
  179. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  180. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
  181. package/dist/astro/routes/api/import/wordpress/execute.mjs +15 -14
  182. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  183. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  184. package/dist/astro/routes/api/import/wordpress/media.mjs +11 -11
  185. package/dist/astro/routes/api/import/wordpress/prepare.mjs +10 -10
  186. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
  187. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +13 -13
  188. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  189. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +18 -18
  190. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
  191. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +29 -28
  192. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  193. package/dist/astro/routes/api/manifest.mjs +4 -4
  194. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  195. package/dist/astro/routes/api/mcp.mjs +213 -74
  196. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  197. package/dist/astro/routes/api/media/_id_/confirm.mjs +11 -11
  198. package/dist/astro/routes/api/media/_id_/replace.d.mts +8 -0
  199. package/dist/astro/routes/api/media/_id_/replace.d.mts.map +1 -0
  200. package/dist/astro/routes/api/media/_id_/replace.mjs +91 -0
  201. package/dist/astro/routes/api/media/_id_/replace.mjs.map +1 -0
  202. package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
  203. package/dist/astro/routes/api/media/_id_/usage.mjs +19 -19
  204. package/dist/astro/routes/api/media/_id_.mjs +18 -18
  205. package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
  206. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -2
  207. package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
  208. package/dist/astro/routes/api/media/folders/_id_.mjs +8 -8
  209. package/dist/astro/routes/api/media/folders/index.mjs +8 -8
  210. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  211. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  212. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  213. package/dist/astro/routes/api/media/upload-url.mjs +16 -14
  214. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  215. package/dist/astro/routes/api/media.d.mts.map +1 -1
  216. package/dist/astro/routes/api/media.mjs +53 -30
  217. package/dist/astro/routes/api/media.mjs.map +1 -1
  218. package/dist/astro/routes/api/menus/_name_/items/_id_.d.mts.map +1 -1
  219. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +20 -13
  220. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  221. package/dist/astro/routes/api/menus/_name_/items.d.mts.map +1 -1
  222. package/dist/astro/routes/api/menus/_name_/items.mjs +15 -11
  223. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  224. package/dist/astro/routes/api/menus/_name_/reorder.d.mts.map +1 -1
  225. package/dist/astro/routes/api/menus/_name_/reorder.mjs +15 -11
  226. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  227. package/dist/astro/routes/api/menus/_name_/translations.d.mts.map +1 -1
  228. package/dist/astro/routes/api/menus/_name_/translations.mjs +16 -12
  229. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  230. package/dist/astro/routes/api/menus/_name_.d.mts.map +1 -1
  231. package/dist/astro/routes/api/menus/_name_.mjs +21 -14
  232. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  233. package/dist/astro/routes/api/menus/index.d.mts.map +1 -1
  234. package/dist/astro/routes/api/menus/index.mjs +15 -11
  235. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  236. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  237. package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
  238. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  239. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  240. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  241. package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
  242. package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
  243. package/dist/astro/routes/api/oauth/token.mjs +5 -5
  244. package/dist/astro/routes/api/openapi.json.mjs +184 -52
  245. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  246. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  247. package/dist/astro/routes/api/redirects/404s/index.mjs +12 -11
  248. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  249. package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -11
  250. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  251. package/dist/astro/routes/api/redirects/_id_.mjs +13 -12
  252. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  253. package/dist/astro/routes/api/redirects/index.mjs +13 -12
  254. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  255. package/dist/astro/routes/api/relations/_id_/index.mjs +20 -18
  256. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  257. package/dist/astro/routes/api/relations/_id_/translations.mjs +15 -13
  258. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  259. package/dist/astro/routes/api/relations/index.mjs +20 -18
  260. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  261. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  262. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  263. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +57 -54
  264. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  265. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +57 -54
  266. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  267. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +57 -54
  268. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  269. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +57 -54
  270. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  271. package/dist/astro/routes/api/schema/collections/index.mjs +57 -54
  272. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  273. package/dist/astro/routes/api/schema/collections/reorder.mjs +57 -54
  274. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  275. package/dist/astro/routes/api/schema/index.mjs +13 -11
  276. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  277. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +57 -54
  278. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  279. package/dist/astro/routes/api/schema/orphans/index.mjs +57 -54
  280. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  281. package/dist/astro/routes/api/search/enable.mjs +12 -11
  282. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  283. package/dist/astro/routes/api/search/index.mjs +11 -10
  284. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  285. package/dist/astro/routes/api/search/rebuild.mjs +12 -11
  286. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  287. package/dist/astro/routes/api/search/stats.mjs +7 -6
  288. package/dist/astro/routes/api/search/stats.mjs.map +1 -1
  289. package/dist/astro/routes/api/search/suggest.mjs +11 -10
  290. package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
  291. package/dist/astro/routes/api/sections/_slug_.mjs +11 -10
  292. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  293. package/dist/astro/routes/api/sections/index.mjs +11 -10
  294. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  295. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -6
  296. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs.map +1 -1
  297. package/dist/astro/routes/api/settings/backups/archives/index.mjs +6 -5
  298. package/dist/astro/routes/api/settings/backups/archives/index.mjs.map +1 -1
  299. package/dist/astro/routes/api/settings/backups/export.mjs +7 -6
  300. package/dist/astro/routes/api/settings/backups/export.mjs.map +1 -1
  301. package/dist/astro/routes/api/settings/backups/index.mjs +8 -7
  302. package/dist/astro/routes/api/settings/backups/index.mjs.map +1 -1
  303. package/dist/astro/routes/api/settings/email.mjs +5 -5
  304. package/dist/astro/routes/api/settings/email.mjs.map +1 -1
  305. package/dist/astro/routes/api/settings.d.mts.map +1 -1
  306. package/dist/astro/routes/api/settings.mjs +21 -17
  307. package/dist/astro/routes/api/settings.mjs.map +1 -1
  308. package/dist/astro/routes/api/setup/admin-verify.mjs +12 -12
  309. package/dist/astro/routes/api/setup/admin.mjs +11 -11
  310. package/dist/astro/routes/api/setup/dev-bypass.mjs +35 -33
  311. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  312. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  313. package/dist/astro/routes/api/setup/index.mjs +35 -33
  314. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  315. package/dist/astro/routes/api/setup/status.mjs +3 -3
  316. package/dist/astro/routes/api/snapshot.mjs +8 -7
  317. package/dist/astro/routes/api/snapshot.mjs.map +1 -1
  318. package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts.map +1 -1
  319. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +22 -17
  320. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
  321. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.d.mts.map +1 -1
  322. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +23 -18
  323. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  324. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts.map +1 -1
  325. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +27 -19
  326. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  327. package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
  328. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +22 -17
  329. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  330. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +16 -15
  331. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -1
  332. package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -1
  333. package/dist/astro/routes/api/taxonomies/_name_.mjs +28 -20
  334. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
  335. package/dist/astro/routes/api/taxonomies/index.d.mts.map +1 -1
  336. package/dist/astro/routes/api/taxonomies/index.mjs +22 -17
  337. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  338. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  339. package/dist/astro/routes/api/typegen.mjs +4 -4
  340. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  341. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  342. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  343. package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts.map +1 -1
  344. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +10 -8
  345. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  346. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts.map +1 -1
  347. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +18 -14
  348. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  349. package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts.map +1 -1
  350. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +16 -13
  351. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  352. package/dist/astro/routes/api/widget-areas/_name_.d.mts.map +1 -1
  353. package/dist/astro/routes/api/widget-areas/_name_.mjs +9 -7
  354. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  355. package/dist/astro/routes/api/widget-areas/index.d.mts.map +1 -1
  356. package/dist/astro/routes/api/widget-areas/index.mjs +16 -13
  357. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  358. package/dist/astro/routes/api/widget-components.mjs +2 -2
  359. package/dist/astro/routes/robots.txt.mjs +8 -8
  360. package/dist/astro/routes/sitemap-_collection_.xml.mjs +13 -12
  361. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  362. package/dist/astro/routes/sitemap.xml.mjs +9 -9
  363. package/dist/astro/types.d.mts +50 -20
  364. package/dist/astro/types.d.mts.map +1 -1
  365. package/dist/auth/providers/github.d.mts +1 -1
  366. package/dist/auth/providers/google.d.mts +1 -1
  367. package/dist/{authorize-ByYs-Uxs.mjs → authorize-C2QHlWSS.mjs} +2 -2
  368. package/dist/{authorize-ByYs-Uxs.mjs.map → authorize-C2QHlWSS.mjs.map} +1 -1
  369. package/dist/{backup-ChyHfIoX.mjs → backup-oS8ft5Cm.mjs} +5 -5
  370. package/dist/{backup-ChyHfIoX.mjs.map → backup-oS8ft5Cm.mjs.map} +1 -1
  371. package/dist/{bundle-DZFhFurR.mjs → bundle-CL0JjIPx.mjs} +4 -4
  372. package/dist/{bundle-DZFhFurR.mjs.map → bundle-CL0JjIPx.mjs.map} +1 -1
  373. package/dist/{byline-Cpnwrje1.mjs → byline-Vdi85CsZ.mjs} +27 -14
  374. package/dist/byline-Vdi85CsZ.mjs.map +1 -0
  375. package/dist/{byline-fields-CAyA_Umd.mjs → byline-fields-DPVRFMz2.mjs} +2 -2
  376. package/dist/{byline-fields-CAyA_Umd.mjs.map → byline-fields-DPVRFMz2.mjs.map} +1 -1
  377. package/dist/{byline-registry-Bpu9-MVs.mjs → byline-registry-BxDGIO0I.mjs} +2 -2
  378. package/dist/{byline-registry-Bpu9-MVs.mjs.map → byline-registry-BxDGIO0I.mjs.map} +1 -1
  379. package/dist/{bylines-BSo9B9RX.mjs → bylines-BX8QhcTk.mjs} +7 -7
  380. package/dist/{bylines-BSo9B9RX.mjs.map → bylines-BX8QhcTk.mjs.map} +1 -1
  381. package/dist/{bylines-CHjvehVX.mjs → bylines-w-E1IEQd.mjs} +3 -3
  382. package/dist/{bylines-CHjvehVX.mjs.map → bylines-w-E1IEQd.mjs.map} +1 -1
  383. package/dist/{cache-5VirJ2Cn.mjs → cache--7HKu5Xm.mjs} +4 -4
  384. package/dist/{cache-5VirJ2Cn.mjs.map → cache--7HKu5Xm.mjs.map} +1 -1
  385. package/dist/{capture-triggers-CNW1q3HK.mjs → capture-triggers-CeAsC76Z.mjs} +2 -2
  386. package/dist/{capture-triggers-CNW1q3HK.mjs.map → capture-triggers-CeAsC76Z.mjs.map} +1 -1
  387. package/dist/{challenge-store-D5pSY2S1.mjs → challenge-store-BIOak-tp.mjs} +5 -3
  388. package/dist/challenge-store-BIOak-tp.mjs.map +1 -0
  389. package/dist/chrome-tags-DzDQBCjX.mjs +24 -0
  390. package/dist/chrome-tags-DzDQBCjX.mjs.map +1 -0
  391. package/dist/{chunks-bNiX6_DR.mjs → chunks-VG_tyqnP.mjs} +2 -2
  392. package/dist/{chunks-bNiX6_DR.mjs.map → chunks-VG_tyqnP.mjs.map} +1 -1
  393. package/dist/cli/index.mjs +68 -40
  394. package/dist/cli/index.mjs.map +1 -1
  395. package/dist/client/cf-access.d.mts +1 -1
  396. package/dist/client/index.d.mts +17 -7
  397. package/dist/client/index.d.mts.map +1 -1
  398. package/dist/client/index.mjs +17 -12
  399. package/dist/client/index.mjs.map +1 -1
  400. package/dist/{collection-deletion-m_oi2c0N.mjs → collection-deletion-BCwaWTDw.mjs} +4 -4
  401. package/dist/{collection-deletion-m_oi2c0N.mjs.map → collection-deletion-BCwaWTDw.mjs.map} +1 -1
  402. package/dist/collection-slugs-cache-7lTmNzeA.mjs +85 -0
  403. package/dist/collection-slugs-cache-7lTmNzeA.mjs.map +1 -0
  404. package/dist/{comment-reaction-CRuItEjt.mjs → comment-reaction-CDyvPQRj.mjs} +2 -2
  405. package/dist/{comment-reaction-CRuItEjt.mjs.map → comment-reaction-CDyvPQRj.mjs.map} +1 -1
  406. package/dist/{comment-DhvLEdhQ.mjs → comment-vxOzWbZN.mjs} +2 -2
  407. package/dist/{comment-DhvLEdhQ.mjs.map → comment-vxOzWbZN.mjs.map} +1 -1
  408. package/dist/{comments-CLj4bi8I.mjs → comments-VzmVLyaC.mjs} +2 -2
  409. package/dist/{comments-CLj4bi8I.mjs.map → comments-VzmVLyaC.mjs.map} +1 -1
  410. package/dist/{components-v4U4DT2y.mjs → components-CL6Wkdk-.mjs} +1 -1
  411. package/dist/{components-v4U4DT2y.mjs.map → components-CL6Wkdk-.mjs.map} +1 -1
  412. package/dist/config--eWjQJE5.mjs +217 -0
  413. package/dist/config--eWjQJE5.mjs.map +1 -0
  414. package/dist/{index-DWbJrsAg.d.mts → config-CGAhyOjP.d.mts} +276 -76
  415. package/dist/config-CGAhyOjP.d.mts.map +1 -0
  416. package/dist/{config-DrfghTPe.d.mts → config-ChFzshGd.d.mts} +1 -1
  417. package/dist/{config-DrfghTPe.d.mts.map → config-ChFzshGd.d.mts.map} +1 -1
  418. package/dist/{content-CP-SeXNa.mjs → content-CNfMWuhY.mjs} +202 -46
  419. package/dist/content-CNfMWuhY.mjs.map +1 -0
  420. package/dist/{content-list-query-BhejKVqb.d.mts → content-list-query-B8d4UQp9.d.mts} +1 -1
  421. package/dist/{content-list-query-BhejKVqb.d.mts.map → content-list-query-B8d4UQp9.d.mts.map} +1 -1
  422. package/dist/{content-refresh-DLOfIhYc.mjs → content-refresh-GTMyi2Xy.mjs} +53 -26
  423. package/dist/content-refresh-GTMyi2Xy.mjs.map +1 -0
  424. package/dist/{content-repair-BCxzy0r4.mjs → content-repair-CIMt5C0U.mjs} +4 -4
  425. package/dist/{content-repair-BCxzy0r4.mjs.map → content-repair-CIMt5C0U.mjs.map} +1 -1
  426. package/dist/{context-BR5hRWiK.mjs → context-C9PB8vGd.mjs} +308 -33
  427. package/dist/context-C9PB8vGd.mjs.map +1 -0
  428. package/dist/{cron-CWfWrWKG.mjs → cron-DHAy9oyq.mjs} +1 -1
  429. package/dist/{cron-CWfWrWKG.mjs.map → cron-DHAy9oyq.mjs.map} +1 -1
  430. package/dist/{dashboard-CkIV2rnQ.mjs → dashboard-BNSvAN8A.mjs} +5 -5
  431. package/dist/{dashboard-CkIV2rnQ.mjs.map → dashboard-BNSvAN8A.mjs.map} +1 -1
  432. package/dist/db/index.d.mts +3 -3
  433. package/dist/db/index.mjs +2 -2
  434. package/dist/db/index.mjs.map +1 -1
  435. package/dist/db/libsql-migrations.d.mts +1 -1
  436. package/dist/db/libsql-migrations.mjs +5 -5
  437. package/dist/db/libsql.d.mts +1 -1
  438. package/dist/db/postgres-migrations.d.mts +1 -1
  439. package/dist/db/postgres-migrations.mjs +5 -5
  440. package/dist/db/postgres.d.mts +1 -1
  441. package/dist/db/postgres.d.mts.map +1 -1
  442. package/dist/db/postgres.mjs +19 -3
  443. package/dist/db/postgres.mjs.map +1 -1
  444. package/dist/db/sqlite-migrations.d.mts +1 -1
  445. package/dist/db/sqlite-migrations.mjs +5 -5
  446. package/dist/db/sqlite.d.mts +1 -1
  447. package/dist/db/sqlite.mjs +1 -1
  448. package/dist/{default-YMEfcQUx.mjs → default-ByZ0JqZo.mjs} +1 -1
  449. package/dist/{default-YMEfcQUx.mjs.map → default-ByZ0JqZo.mjs.map} +1 -1
  450. package/dist/{deferred-tasks-DfqxH5Xf.d.mts → deferred-tasks-Cu4NSJpa.d.mts} +1 -1
  451. package/dist/{deferred-tasks-DfqxH5Xf.d.mts.map → deferred-tasks-Cu4NSJpa.d.mts.map} +1 -1
  452. package/dist/dev-typegen-DHT7lcMU.mjs +29 -0
  453. package/dist/dev-typegen-DHT7lcMU.mjs.map +1 -0
  454. package/dist/{device-flow-CojjSBwb.mjs → device-flow-Cf6bhCCT.mjs} +4 -4
  455. package/dist/{device-flow-CojjSBwb.mjs.map → device-flow-Cf6bhCCT.mjs.map} +1 -1
  456. package/dist/{dialect-helpers-BSbfaO3F.mjs → dialect-helpers-BcQvkHgn.mjs} +83 -2
  457. package/dist/dialect-helpers-BcQvkHgn.mjs.map +1 -0
  458. package/dist/{direct-executor-BWeWiRYB.mjs → direct-executor-yAyUGSqB.mjs} +3 -3
  459. package/dist/{direct-executor-BWeWiRYB.mjs.map → direct-executor-yAyUGSqB.mjs.map} +1 -1
  460. package/dist/{dist-89xCrGqi.mjs → dist-BAsh8dbf.mjs} +1 -1
  461. package/dist/{dist-89xCrGqi.mjs.map → dist-BAsh8dbf.mjs.map} +1 -1
  462. package/dist/{email-console-CEJgR_LB.mjs → email-console-DNup1Fku.mjs} +1 -1
  463. package/dist/{email-console-CEJgR_LB.mjs.map → email-console-DNup1Fku.mjs.map} +1 -1
  464. package/dist/{enrich-BX0IDp4z.mjs → enrich-t2Xvjdqa.mjs} +3 -3
  465. package/dist/{enrich-BX0IDp4z.mjs.map → enrich-t2Xvjdqa.mjs.map} +1 -1
  466. package/dist/entry-lock-BEr8gDkG.mjs +163 -0
  467. package/dist/entry-lock-BEr8gDkG.mjs.map +1 -0
  468. package/dist/{relations-C7B46bcU.d.mts → entry-lock-CGAtrwAR.d.mts} +265 -76
  469. package/dist/entry-lock-CGAtrwAR.d.mts.map +1 -0
  470. package/dist/{redirects-CRIM55KH.mjs → entry-lock-DIYHAaSw.mjs} +112 -34
  471. package/dist/entry-lock-DIYHAaSw.mjs.map +1 -0
  472. package/dist/entry-locks-BEcX7ET0.mjs +167 -0
  473. package/dist/entry-locks-BEcX7ET0.mjs.map +1 -0
  474. package/dist/{error-D9w94fjV.mjs → error-BDcFyoRt.mjs} +2 -2
  475. package/dist/{error-D9w94fjV.mjs.map → error-BDcFyoRt.mjs.map} +1 -1
  476. package/dist/{errors-n2j49uNf.mjs → errors-DvMdNrgf.mjs} +10 -1
  477. package/dist/errors-DvMdNrgf.mjs.map +1 -0
  478. package/dist/{escape-DN4Qmjsl.mjs → escape-B3asJC6W.mjs} +1 -1
  479. package/dist/{escape-DN4Qmjsl.mjs.map → escape-B3asJC6W.mjs.map} +1 -1
  480. package/dist/{field-defs-cache-Bh4q6oar.mjs → field-defs-cache-Cf5FHK0M.mjs} +5 -5
  481. package/dist/{field-defs-cache-Bh4q6oar.mjs.map → field-defs-cache-Cf5FHK0M.mjs.map} +1 -1
  482. package/dist/{fts-manager-BhOdW0bi.mjs → fts-manager-iddym8cd.mjs} +4 -4
  483. package/dist/{fts-manager-BhOdW0bi.mjs.map → fts-manager-iddym8cd.mjs.map} +1 -1
  484. package/dist/{hash-CTcngWvj.mjs → hash-BRweX6jz.mjs} +1 -1
  485. package/dist/{hash-CTcngWvj.mjs.map → hash-BRweX6jz.mjs.map} +1 -1
  486. package/dist/hook-result-BrshYHLx.d.mts +31 -0
  487. package/dist/hook-result-BrshYHLx.d.mts.map +1 -0
  488. package/dist/{hreflang-7pTHsLZJ.d.mts → hreflang-CQpu5bMy.d.mts} +2 -2
  489. package/dist/{hreflang-7pTHsLZJ.d.mts.map → hreflang-CQpu5bMy.d.mts.map} +1 -1
  490. package/dist/{hreflang-Huc6O5wa.mjs → hreflang-DshnweTC.mjs} +9 -8
  491. package/dist/{hreflang-Huc6O5wa.mjs.map → hreflang-DshnweTC.mjs.map} +1 -1
  492. package/dist/{identity-DXmb4aVp.mjs → identity-CjGKDq8J.mjs} +3 -3
  493. package/dist/{identity-DXmb4aVp.mjs.map → identity-CjGKDq8J.mjs.map} +1 -1
  494. package/dist/{import-C4_O3KXU.mjs → import-D96g2otw.mjs} +6 -6
  495. package/dist/{import-C4_O3KXU.mjs.map → import-D96g2otw.mjs.map} +1 -1
  496. package/dist/{index-6bRee8zb.d.mts → index-1ECYpfiF.d.mts} +3 -3
  497. package/dist/{index-6bRee8zb.d.mts.map → index-1ECYpfiF.d.mts.map} +1 -1
  498. package/dist/index.d.mts +22 -21
  499. package/dist/index.mjs +84 -81
  500. package/dist/{init-lock-DJkX6Hto.mjs → init-lock-CFNBe_iH.mjs} +1 -1
  501. package/dist/{init-lock-DJkX6Hto.mjs.map → init-lock-CFNBe_iH.mjs.map} +1 -1
  502. package/dist/{load-Bo7fZAbg.mjs → load-DPtY9cZo.mjs} +2 -2
  503. package/dist/{load-Bo7fZAbg.mjs.map → load-DPtY9cZo.mjs.map} +1 -1
  504. package/dist/{loader-ZsjE4paL.mjs → loader-WJi6GFRn.mjs} +90 -17
  505. package/dist/loader-WJi6GFRn.mjs.map +1 -0
  506. package/dist/{manifest-builder-QXuqvVMP.mjs → manifest-builder-BfNlGQIA.mjs} +2 -2
  507. package/dist/{manifest-builder-QXuqvVMP.mjs.map → manifest-builder-BfNlGQIA.mjs.map} +1 -1
  508. package/dist/{manifest-schema-CUYPfCEH.mjs → manifest-schema-CILooI5J.mjs} +5 -5
  509. package/dist/manifest-schema-CILooI5J.mjs.map +1 -0
  510. package/dist/media/image-endpoint.d.mts +4 -2
  511. package/dist/media/image-endpoint.d.mts.map +1 -1
  512. package/dist/media/image-endpoint.mjs +6 -4
  513. package/dist/media/image-endpoint.mjs.map +1 -1
  514. package/dist/media/index.d.mts +1 -1
  515. package/dist/media/index.mjs +3 -3
  516. package/dist/media/local-runtime.d.mts +12 -12
  517. package/dist/media/local-runtime.mjs +7 -7
  518. package/dist/{media-D6SeAfBl.mjs → media-BBm8Wg3f.mjs} +30 -2
  519. package/dist/media-BBm8Wg3f.mjs.map +1 -0
  520. package/dist/{media-BjwNuLEu.mjs → media-C1EIZKh1.mjs} +35 -5
  521. package/dist/media-C1EIZKh1.mjs.map +1 -0
  522. package/dist/{media-allowlist-D35v7bmL.mjs → media-allowlist-elXJgb_K.mjs} +2 -2
  523. package/dist/{media-allowlist-D35v7bmL.mjs.map → media-allowlist-elXJgb_K.mjs.map} +1 -1
  524. package/dist/{media-folders-D-0CmRZj.mjs → media-folders-DASfX_DH.mjs} +1 -1
  525. package/dist/{media-folders-D-0CmRZj.mjs.map → media-folders-DASfX_DH.mjs.map} +1 -1
  526. package/dist/{media-upload-C7bLXGcI.mjs → media-upload-C7_AQJ5v.mjs} +9 -9
  527. package/dist/{media-upload-C7bLXGcI.mjs.map → media-upload-C7_AQJ5v.mjs.map} +1 -1
  528. package/dist/{media-url-DH7giMx_.mjs → media-url-8dVl1loq.mjs} +19 -2
  529. package/dist/media-url-8dVl1loq.mjs.map +1 -0
  530. package/dist/{media-usage-BTRJvS6V.mjs → media-usage-C1EswMTq.mjs} +10 -10
  531. package/dist/{media-usage-BTRJvS6V.mjs.map → media-usage-C1EswMTq.mjs.map} +1 -1
  532. package/dist/{media-usage-activation-Dx0kPV6b.mjs → media-usage-activation-CrHNltlL.mjs} +3 -3
  533. package/dist/{media-usage-activation-Dx0kPV6b.mjs.map → media-usage-activation-CrHNltlL.mjs.map} +1 -1
  534. package/dist/{media-usage-work-C-fhM30c.mjs → media-usage-work-C5B1zT1c.mjs} +2 -2
  535. package/dist/{media-usage-work-C-fhM30c.mjs.map → media-usage-work-C5B1zT1c.mjs.map} +1 -1
  536. package/dist/{media-usage-work-DWb2XPMI.mjs → media-usage-work-IkQx_Yz6.mjs} +4 -4
  537. package/dist/{media-usage-work-DWb2XPMI.mjs.map → media-usage-work-IkQx_Yz6.mjs.map} +1 -1
  538. package/dist/{media-usage-write-fence-BwoJVRDC.mjs → media-usage-write-fence-B9anjOSg.mjs} +3 -3
  539. package/dist/{media-usage-write-fence-BwoJVRDC.mjs.map → media-usage-write-fence-B9anjOSg.mjs.map} +1 -1
  540. package/dist/{menus-CNg0QlDt.mjs → menus-D8mC4eaN.mjs} +406 -175
  541. package/dist/menus-D8mC4eaN.mjs.map +1 -0
  542. package/dist/{menus-CD7EFLwz.mjs → menus-UdwjHZJC.mjs} +12 -4
  543. package/dist/{menus-CD7EFLwz.mjs.map → menus-UdwjHZJC.mjs.map} +1 -1
  544. package/dist/migrations/index.d.mts +1 -1
  545. package/dist/migrations/index.mjs +4 -4
  546. package/dist/{mime-CUIG6x84.mjs → mime-DYpsOAny.mjs} +1 -1
  547. package/dist/{mime-CUIG6x84.mjs.map → mime-DYpsOAny.mjs.map} +1 -1
  548. package/dist/{mode-DaUM_MB2.mjs → mode-Dm4BHhRH.mjs} +1 -1
  549. package/dist/{mode-DaUM_MB2.mjs.map → mode-Dm4BHhRH.mjs.map} +1 -1
  550. package/dist/{node-sqlite-compat-Udb4Y-rs.mjs → node-sqlite-compat-2sezm-Zd.mjs} +1 -1
  551. package/dist/{node-sqlite-compat-Udb4Y-rs.mjs.map → node-sqlite-compat-2sezm-Zd.mjs.map} +1 -1
  552. package/dist/{normalize-CSxdKBVa.mjs → normalize-4nkKGsuW.mjs} +15 -5
  553. package/dist/normalize-4nkKGsuW.mjs.map +1 -0
  554. package/dist/{oauth-authorization-Dpsb-fMJ.mjs → oauth-authorization-BxE_lURD.mjs} +4 -4
  555. package/dist/{oauth-authorization-Dpsb-fMJ.mjs.map → oauth-authorization-BxE_lURD.mjs.map} +1 -1
  556. package/dist/{oauth-clients-Bxnoe_67.mjs → oauth-clients-DJIdOdq0.mjs} +1 -1
  557. package/dist/{oauth-clients-Bxnoe_67.mjs.map → oauth-clients-DJIdOdq0.mjs.map} +1 -1
  558. package/dist/{oauth-state-store-0C4s0K6w.mjs → oauth-state-store-CsdxP57n.mjs} +1 -1
  559. package/dist/{oauth-state-store-0C4s0K6w.mjs.map → oauth-state-store-CsdxP57n.mjs.map} +1 -1
  560. package/dist/{oauth-user-lookup-wlGYSeqX.mjs → oauth-user-lookup-HzzW-dkF.mjs} +1 -1
  561. package/dist/{oauth-user-lookup-wlGYSeqX.mjs.map → oauth-user-lookup-HzzW-dkF.mjs.map} +1 -1
  562. package/dist/object-cache/memory.d.mts +1 -1
  563. package/dist/{object-cache-71tPfV4A.mjs → object-cache-DVKnyOfy.mjs} +19 -3
  564. package/dist/object-cache-DVKnyOfy.mjs.map +1 -0
  565. package/dist/{options-BHPeIbBd.mjs → options-D1FBiAv1.mjs} +60 -5
  566. package/dist/options-D1FBiAv1.mjs.map +1 -0
  567. package/dist/{options-BhZIeFY1.d.mts → options-D8Y0VHc7.d.mts} +30 -11
  568. package/dist/options-D8Y0VHc7.d.mts.map +1 -0
  569. package/dist/page/index.d.mts +2 -2
  570. package/dist/page/index.d.mts.map +1 -1
  571. package/dist/page/index.mjs +12 -0
  572. package/dist/page/index.mjs.map +1 -1
  573. package/dist/{parse-BRJg1kz8.mjs → parse-WRg5C3NX.mjs} +2 -2
  574. package/dist/{parse-BRJg1kz8.mjs.map → parse-WRg5C3NX.mjs.map} +1 -1
  575. package/dist/{passkey-config-B3P0nHRR.mjs → passkey-config-qIdO-wF6.mjs} +1 -1
  576. package/dist/{passkey-config-B3P0nHRR.mjs.map → passkey-config-qIdO-wF6.mjs.map} +1 -1
  577. package/dist/{patterns-D9iDO3j8.mjs → patterns-DC8CLptu.mjs} +1 -1
  578. package/dist/{patterns-D9iDO3j8.mjs.map → patterns-DC8CLptu.mjs.map} +1 -1
  579. package/dist/{placeholder-BR4ZhVJl.d.mts → placeholder-COrvARyD.d.mts} +1 -1
  580. package/dist/{placeholder-BR4ZhVJl.d.mts.map → placeholder-COrvARyD.d.mts.map} +1 -1
  581. package/dist/plugin-types.d.mts +8 -3
  582. package/dist/plugin-types.d.mts.map +1 -1
  583. package/dist/plugin-utils.d.mts +10 -10
  584. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  585. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  586. package/dist/{policy-BVRganJQ.mjs → policy-XCfb-IE9.mjs} +2 -2
  587. package/dist/{policy-BVRganJQ.mjs.map → policy-XCfb-IE9.mjs.map} +1 -1
  588. package/dist/{portable-text-C8Tvp4_g.mjs → portable-text-BS3d5n-0.mjs} +1 -1
  589. package/dist/{portable-text-C8Tvp4_g.mjs.map → portable-text-BS3d5n-0.mjs.map} +1 -1
  590. package/dist/{preview-dn2dUyM0.mjs → preview-BxwsGgGp.mjs} +2 -2
  591. package/dist/{preview-dn2dUyM0.mjs.map → preview-BxwsGgGp.mjs.map} +1 -1
  592. package/dist/{protocol-CuBEsqgE.d.mts → protocol-CLn58-nw.d.mts} +2 -2
  593. package/dist/{protocol-CuBEsqgE.d.mts.map → protocol-CLn58-nw.d.mts.map} +1 -1
  594. package/dist/{public-url-DRz_AinV.mjs → public-url-FWWAvnre.mjs} +1 -1
  595. package/dist/{public-url-DRz_AinV.mjs.map → public-url-FWWAvnre.mjs.map} +1 -1
  596. package/dist/{query-D18vTdha.mjs → query-DygOte9M.mjs} +26 -17
  597. package/dist/query-DygOte9M.mjs.map +1 -0
  598. package/dist/{query-tZDEdtCN.mjs → query-gtXT03Wc.mjs} +5 -5
  599. package/dist/{query-tZDEdtCN.mjs.map → query-gtXT03Wc.mjs.map} +1 -1
  600. package/dist/{rate-limit-on7KPo-v.mjs → rate-limit-CQUPm0dh.mjs} +2 -2
  601. package/dist/{rate-limit-on7KPo-v.mjs.map → rate-limit-CQUPm0dh.mjs.map} +1 -1
  602. package/dist/{redirect-DrbEmU9a.mjs → redirect-D1cWafi6.mjs} +1 -1
  603. package/dist/{redirect-DrbEmU9a.mjs.map → redirect-D1cWafi6.mjs.map} +1 -1
  604. package/dist/{redirect-CZkbozu9.mjs → redirect-DId44BS1.mjs} +19 -6
  605. package/dist/redirect-DId44BS1.mjs.map +1 -0
  606. package/dist/{redirects-BahWiu8Z.mjs → redirects-DN-ZE33W.mjs} +5 -5
  607. package/dist/{redirects-BahWiu8Z.mjs.map → redirects-DN-ZE33W.mjs.map} +1 -1
  608. package/dist/{registry-Cx-jEJOS.mjs → registry-DTqaqxed.mjs} +46 -12
  609. package/dist/registry-DTqaqxed.mjs.map +1 -0
  610. package/dist/{relation-A6wLF_DS.mjs → relation-DxgCHOBw.mjs} +2 -2
  611. package/dist/{relation-A6wLF_DS.mjs.map → relation-DxgCHOBw.mjs.map} +1 -1
  612. package/dist/{relations-Cj5hZ6Zg.mjs → relations-KKDSLUwS.mjs} +4 -4
  613. package/dist/{relations-Cj5hZ6Zg.mjs.map → relations-KKDSLUwS.mjs.map} +1 -1
  614. package/dist/{relations-BTPM2saV.mjs → relations-rOmbndUJ.mjs} +8 -8
  615. package/dist/relations-rOmbndUJ.mjs.map +1 -0
  616. package/dist/{request-cache-DH9zdCOD.mjs → request-cache-DAZZmesc.mjs} +1 -1
  617. package/dist/{request-cache-DH9zdCOD.mjs.map → request-cache-DAZZmesc.mjs.map} +1 -1
  618. package/dist/request-context.d.mts +1 -1
  619. package/dist/{request-meta-DRp5ENd_.mjs → request-meta-BgjUQLQ-.mjs} +2 -2
  620. package/dist/{request-meta-DRp5ENd_.mjs.map → request-meta-BgjUQLQ-.mjs.map} +1 -1
  621. package/dist/{resolve-DxUpTMmE.mjs → resolve-Cy0aWSRj.mjs} +37 -4
  622. package/dist/resolve-Cy0aWSRj.mjs.map +1 -0
  623. package/dist/{runner-zgqA_CqP.d.mts → runner-CfwysRM0.d.mts} +3 -2
  624. package/dist/runner-CfwysRM0.d.mts.map +1 -0
  625. package/dist/{runner-77GOZCnH.mjs → runner-nEzvnogG.mjs} +476 -295
  626. package/dist/runner-nEzvnogG.mjs.map +1 -0
  627. package/dist/runtime.d.mts +11 -11
  628. package/dist/runtime.mjs +3 -2
  629. package/dist/runtime.mjs.map +1 -1
  630. package/dist/{schema-BY-oVntF.mjs → schema-CRu38Pr5.mjs} +3 -3
  631. package/dist/{schema-BY-oVntF.mjs.map → schema-CRu38Pr5.mjs.map} +1 -1
  632. package/dist/{schema-Cw9kVK0I.mjs → schema-DifxndJx.mjs} +5 -5
  633. package/dist/{schema-Cw9kVK0I.mjs.map → schema-DifxndJx.mjs.map} +1 -1
  634. package/dist/{scopes-vMR1uIbt.mjs → scopes-D5A1OZbq.mjs} +3 -3
  635. package/dist/{scopes-vMR1uIbt.mjs.map → scopes-D5A1OZbq.mjs.map} +1 -1
  636. package/dist/{search-CfT3KxO5.mjs → search-DEPFSBFq.mjs} +4 -4
  637. package/dist/{search-CfT3KxO5.mjs.map → search-DEPFSBFq.mjs.map} +1 -1
  638. package/dist/{secrets-BH2Qow1-.mjs → secrets-CdfzLdoO.mjs} +3 -3
  639. package/dist/{secrets-BH2Qow1-.mjs.map → secrets-CdfzLdoO.mjs.map} +1 -1
  640. package/dist/{sections-DTfjyf1z.mjs → sections-3X52kmIR.mjs} +2 -2
  641. package/dist/{sections-DTfjyf1z.mjs.map → sections-3X52kmIR.mjs.map} +1 -1
  642. package/dist/seed/index.d.mts +2 -2
  643. package/dist/seed/index.mjs +28 -26
  644. package/dist/seo/index.d.mts +3 -3
  645. package/dist/seo/index.d.mts.map +1 -1
  646. package/dist/seo/index.mjs +20 -21
  647. package/dist/seo/index.mjs.map +1 -1
  648. package/dist/{seo-Cm-dtvgk.mjs → seo-Bw68KxRc.mjs} +3 -3
  649. package/dist/{seo-Cm-dtvgk.mjs.map → seo-Bw68KxRc.mjs.map} +1 -1
  650. package/dist/{seo-BSiTIXzd.mjs → seo-C4AmbsMU.mjs} +3 -2
  651. package/dist/seo-C4AmbsMU.mjs.map +1 -0
  652. package/dist/{service-DYsDHEKe.mjs → service-J_-Vgo7R.mjs} +3 -3
  653. package/dist/{service-DYsDHEKe.mjs.map → service-J_-Vgo7R.mjs.map} +1 -1
  654. package/dist/{session-user-CNAfvsdw.mjs → session-user-CbnMMwk6.mjs} +1 -1
  655. package/dist/{session-user-CNAfvsdw.mjs.map → session-user-CbnMMwk6.mjs.map} +1 -1
  656. package/dist/{settings-DjtkUo__.mjs → settings-AnEFYICn.mjs} +3 -3
  657. package/dist/{settings-DjtkUo__.mjs.map → settings-AnEFYICn.mjs.map} +1 -1
  658. package/dist/{settings-BgN-0DT-.mjs → settings-DcMjREe5.mjs} +21 -8
  659. package/dist/settings-DcMjREe5.mjs.map +1 -0
  660. package/dist/{setup-complete-CcpXaPU7.mjs → setup-complete-BClXXTvr.mjs} +2 -2
  661. package/dist/{setup-complete-CcpXaPU7.mjs.map → setup-complete-BClXXTvr.mjs.map} +1 -1
  662. package/dist/{setup-nonce-Reyw30jg.mjs → setup-nonce-BheRa4RG.mjs} +1 -1
  663. package/dist/{setup-nonce-Reyw30jg.mjs.map → setup-nonce-BheRa4RG.mjs.map} +1 -1
  664. package/dist/{single-flight-cache-BPuGPdcN.mjs → single-flight-cache-jaswDzmQ.mjs} +2 -2
  665. package/dist/{single-flight-cache-BPuGPdcN.mjs.map → single-flight-cache-jaswDzmQ.mjs.map} +1 -1
  666. package/dist/{site-url-Cs_V9n3V.mjs → site-url-Bqf-ea3N.mjs} +3 -3
  667. package/dist/{site-url-Cs_V9n3V.mjs.map → site-url-Bqf-ea3N.mjs.map} +1 -1
  668. package/dist/{snapshot-DSB8P7Lm.mjs → snapshot-C4CtXntb.mjs} +51 -35
  669. package/dist/snapshot-C4CtXntb.mjs.map +1 -0
  670. package/dist/{ssrf-DqebVE34.mjs → ssrf-gl4gFNhw.mjs} +151 -93
  671. package/dist/ssrf-gl4gFNhw.mjs.map +1 -0
  672. package/dist/{state-BJfU9Uzw.mjs → state-1F6IOhdQ.mjs} +1 -1
  673. package/dist/{state-BJfU9Uzw.mjs.map → state-1F6IOhdQ.mjs.map} +1 -1
  674. package/dist/{status-DDu8HIQl.mjs → status-Cenfk_r5.mjs} +1 -1
  675. package/dist/{status-DDu8HIQl.mjs.map → status-Cenfk_r5.mjs.map} +1 -1
  676. package/dist/storage/local.d.mts +2 -1
  677. package/dist/storage/local.d.mts.map +1 -1
  678. package/dist/storage/local.mjs +1 -1
  679. package/dist/storage/local.mjs.map +1 -1
  680. package/dist/storage/s3.d.mts +2 -1
  681. package/dist/storage/s3.d.mts.map +1 -1
  682. package/dist/storage/s3.mjs +3 -2
  683. package/dist/storage/s3.mjs.map +1 -1
  684. package/dist/{target-D0o26Azo.mjs → target-CRzeTs8G.mjs} +1 -1
  685. package/dist/{target-D0o26Azo.mjs.map → target-CRzeTs8G.mjs.map} +1 -1
  686. package/dist/{taxonomies-CPGxo-bn.mjs → taxonomies-BOdxuZpV.mjs} +23 -13
  687. package/dist/taxonomies-BOdxuZpV.mjs.map +1 -0
  688. package/dist/{taxonomies-kmyudNGP.mjs → taxonomies-NcFDq5Gg.mjs} +58 -23
  689. package/dist/taxonomies-NcFDq5Gg.mjs.map +1 -0
  690. package/dist/{taxonomy-8X09dLN5.mjs → taxonomy-DP1vny4K.mjs} +12 -4
  691. package/dist/{taxonomy-8X09dLN5.mjs.map → taxonomy-DP1vny4K.mjs.map} +1 -1
  692. package/dist/{tokens-gTS-IVUp.mjs → tokens-CDk8ox9p.mjs} +1 -1
  693. package/dist/{tokens-gTS-IVUp.mjs.map → tokens-CDk8ox9p.mjs.map} +1 -1
  694. package/dist/{transport-_6R1ub7t.d.mts → transport-BVn4tVb9.d.mts} +1 -1
  695. package/dist/{transport-_6R1ub7t.d.mts.map → transport-BVn4tVb9.d.mts.map} +1 -1
  696. package/dist/{transport-fX59xaJz.mjs → transport-DP0JI-8i.mjs} +1 -1
  697. package/dist/{transport-fX59xaJz.mjs.map → transport-DP0JI-8i.mjs.map} +1 -1
  698. package/dist/{trusted-proxy-fe6nEgR4.mjs → trusted-proxy-BTM6UxqD.mjs} +1 -1
  699. package/dist/{trusted-proxy-fe6nEgR4.mjs.map → trusted-proxy-BTM6UxqD.mjs.map} +1 -1
  700. package/dist/{types-D_DWe7k9.d.mts → types-2n848aVW.d.mts} +3 -2
  701. package/dist/types-2n848aVW.d.mts.map +1 -0
  702. package/dist/types-A2cN6a4a.mjs +3 -0
  703. package/dist/{types-75KIqzL9.mjs → types-BMXnRIrX.mjs} +4 -2
  704. package/dist/types-BMXnRIrX.mjs.map +1 -0
  705. package/dist/{types-Vg6f5i3s.d.mts → types-BouXpOsa.d.mts} +121 -4
  706. package/dist/types-BouXpOsa.d.mts.map +1 -0
  707. package/dist/{types-BXUmsINL.mjs → types-ByG2coLw.mjs} +1 -1
  708. package/dist/{types-BXUmsINL.mjs.map → types-ByG2coLw.mjs.map} +1 -1
  709. package/dist/types-CQPWhPxE.mjs.map +1 -1
  710. package/dist/{types-Blb-MRC3.d.mts → types-DEfNJ__A.d.mts} +1 -1
  711. package/dist/{types-Blb-MRC3.d.mts.map → types-DEfNJ__A.d.mts.map} +1 -1
  712. package/dist/{types-Br-ERaxh.d.mts → types-DKcKSbtB.d.mts} +23 -7
  713. package/dist/types-DKcKSbtB.d.mts.map +1 -0
  714. package/dist/{types-BzBd3PeX.d.mts → types-DRYs7uTL.d.mts} +1 -1
  715. package/dist/{types-BzBd3PeX.d.mts.map → types-DRYs7uTL.d.mts.map} +1 -1
  716. package/dist/{types-CLqvnd05.d.mts → types-DaEg7KX2.d.mts} +1 -1
  717. package/dist/{types-CLqvnd05.d.mts.map → types-DaEg7KX2.d.mts.map} +1 -1
  718. package/dist/{types-DJQxln7q.d.mts → types-LU8utpP_.d.mts} +14 -10
  719. package/dist/types-LU8utpP_.d.mts.map +1 -0
  720. package/dist/{types-D5VVLr7J.d.mts → types-VpYfPUxN.d.mts} +1 -1
  721. package/dist/{types-D5VVLr7J.d.mts.map → types-VpYfPUxN.d.mts.map} +1 -1
  722. package/dist/{types-DM3vioBY.mjs → types-c9GP7Dj5.mjs} +1 -1
  723. package/dist/{types-DM3vioBY.mjs.map → types-c9GP7Dj5.mjs.map} +1 -1
  724. package/dist/{types-DHxzBjbD.d.mts → types-tJWDK__M.d.mts} +14 -1
  725. package/dist/types-tJWDK__M.d.mts.map +1 -0
  726. package/dist/{upload-attempts-CNyGSepg.mjs → upload-attempts-C6S2y-lp.mjs} +1 -1
  727. package/dist/{upload-attempts-CNyGSepg.mjs.map → upload-attempts-C6S2y-lp.mjs.map} +1 -1
  728. package/dist/{url-pattern-K-KdXcSe.mjs → url-pattern-UYtxVTuj.mjs} +1 -1
  729. package/dist/{url-pattern-K-KdXcSe.mjs.map → url-pattern-UYtxVTuj.mjs.map} +1 -1
  730. package/dist/{user-CtYY0tp2.mjs → user-DIsIXogT.mjs} +2 -2
  731. package/dist/{user-CtYY0tp2.mjs.map → user-DIsIXogT.mjs.map} +1 -1
  732. package/dist/{utils-HztC73hq.mjs → utils-BicP5Qbg.mjs} +2 -2
  733. package/dist/{utils-HztC73hq.mjs.map → utils-BicP5Qbg.mjs.map} +1 -1
  734. package/dist/{validate-Dd-IUjnZ.mjs → validate-D8VPn4Hv.mjs} +8 -3
  735. package/dist/validate-D8VPn4Hv.mjs.map +1 -0
  736. package/dist/{validate-DlqdOZXB.d.mts → validate-Df6Dvwbw.d.mts} +12 -7
  737. package/dist/validate-Df6Dvwbw.d.mts.map +1 -0
  738. package/dist/{validation-D83n7Npi.mjs → validation-CzMJn9Ku.mjs} +13 -11
  739. package/dist/{validation-D83n7Npi.mjs.map → validation-CzMJn9Ku.mjs.map} +1 -1
  740. package/dist/version-CK67_SXX.mjs +7 -0
  741. package/dist/{version-ri-sCyAp.mjs.map → version-CK67_SXX.mjs.map} +1 -1
  742. package/dist/{widgets-CpmZd9Q-.mjs → widgets-RFVAxQl3.mjs} +18 -5
  743. package/dist/widgets-RFVAxQl3.mjs.map +1 -0
  744. package/dist/{wxr-taxonomies-BFd0y72_.d.mts → wxr-taxonomies-BX4yQL9s.d.mts} +1 -1
  745. package/dist/{wxr-taxonomies-BFd0y72_.d.mts.map → wxr-taxonomies-BX4yQL9s.d.mts.map} +1 -1
  746. package/dist/{wxr-taxonomies-Df4vxWcU.mjs → wxr-taxonomies-Csdp7yde.mjs} +4 -4
  747. package/dist/{wxr-taxonomies-Df4vxWcU.mjs.map → wxr-taxonomies-Csdp7yde.mjs.map} +1 -1
  748. package/dist/{zod-generator-DylzusHh.mjs → zod-generator-CNRGdnBY.mjs} +11 -7
  749. package/dist/zod-generator-CNRGdnBY.mjs.map +1 -0
  750. package/package.json +16 -11
  751. package/src/api/errors.ts +9 -0
  752. package/src/api/handlers/content.ts +115 -90
  753. package/src/api/handlers/entry-lock.ts +206 -0
  754. package/src/api/handlers/index.ts +1 -0
  755. package/src/api/handlers/manifest.ts +162 -12
  756. package/src/api/handlers/marketplace.ts +28 -20
  757. package/src/api/handlers/media.ts +30 -0
  758. package/src/api/handlers/menus.ts +9 -0
  759. package/src/api/handlers/registry.ts +406 -297
  760. package/src/api/handlers/revision.ts +2 -1
  761. package/src/api/handlers/seo.ts +10 -1
  762. package/src/api/handlers/snapshot.ts +84 -72
  763. package/src/api/handlers/taxonomies.ts +25 -9
  764. package/src/api/openapi/document.ts +196 -11
  765. package/src/api/parse.ts +1 -1
  766. package/src/api/schemas/auth.ts +4 -4
  767. package/src/api/schemas/bylines.ts +3 -3
  768. package/src/api/schemas/comments.ts +1 -1
  769. package/src/api/schemas/common.ts +3 -2
  770. package/src/api/schemas/content.ts +86 -22
  771. package/src/api/schemas/entry-lock.ts +48 -0
  772. package/src/api/schemas/index.ts +1 -0
  773. package/src/api/schemas/media.ts +52 -0
  774. package/src/api/schemas/redirects.ts +6 -1
  775. package/src/api/schemas/schema.ts +14 -1
  776. package/src/api/schemas/setup.ts +1 -1
  777. package/src/api/schemas/taxonomies.ts +2 -2
  778. package/src/api/schemas/users.ts +1 -1
  779. package/src/api/types.ts +25 -10
  780. package/src/astro/dev-typegen.ts +35 -0
  781. package/src/astro/image-endpoint.ts +2 -2
  782. package/src/astro/integration/dev-typegen.ts +91 -0
  783. package/src/astro/integration/index.ts +18 -50
  784. package/src/astro/integration/routes.ts +10 -0
  785. package/src/astro/integration/runtime.ts +13 -5
  786. package/src/astro/integration/vite-config.ts +51 -2
  787. package/src/astro/middleware.ts +21 -0
  788. package/src/astro/routes/api/admin/api-tokens/index.ts +4 -1
  789. package/src/astro/routes/api/admin/oauth-clients/[id].ts +1 -1
  790. package/src/astro/routes/api/admin/oauth-clients/index.ts +1 -1
  791. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
  792. package/src/astro/routes/api/admin/plugins/registry/artifact.ts +3 -7
  793. package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
  794. package/src/astro/routes/api/admin/plugins/registry/verify.ts +60 -0
  795. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +18 -2
  796. package/src/astro/routes/api/content/[collection]/[id]/lock.ts +133 -0
  797. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +12 -1
  798. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +20 -0
  799. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +16 -2
  800. package/src/astro/routes/api/content/[collection]/[id].ts +27 -2
  801. package/src/astro/routes/api/content/[collection]/index.ts +3 -0
  802. package/src/astro/routes/api/manifest.ts +1 -1
  803. package/src/astro/routes/api/media/[id]/replace.ts +114 -0
  804. package/src/astro/routes/api/media/file/[...key].ts +4 -1
  805. package/src/astro/routes/api/media/upload-url.ts +8 -4
  806. package/src/astro/routes/api/media.ts +54 -8
  807. package/src/astro/routes/api/menus/[name]/items/[id].ts +7 -2
  808. package/src/astro/routes/api/menus/[name]/items.ts +4 -1
  809. package/src/astro/routes/api/menus/[name]/reorder.ts +4 -1
  810. package/src/astro/routes/api/menus/[name]/translations.ts +4 -1
  811. package/src/astro/routes/api/menus/[name].ts +7 -2
  812. package/src/astro/routes/api/menus/index.ts +4 -1
  813. package/src/astro/routes/api/settings/email.ts +1 -1
  814. package/src/astro/routes/api/settings.ts +4 -1
  815. package/src/astro/routes/api/taxonomies/[name]/reorder.ts +4 -1
  816. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +4 -1
  817. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +7 -2
  818. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +4 -1
  819. package/src/astro/routes/api/taxonomies/[name].ts +7 -2
  820. package/src/astro/routes/api/taxonomies/index.ts +4 -1
  821. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +3 -1
  822. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +5 -2
  823. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +3 -1
  824. package/src/astro/routes/api/widget-areas/[name].ts +3 -1
  825. package/src/astro/routes/api/widget-areas/index.ts +3 -1
  826. package/src/astro/routes/sitemap-[collection].xml.ts +4 -0
  827. package/src/astro/types.ts +36 -8
  828. package/src/auth/challenge-store.ts +3 -1
  829. package/src/aws-sdk.d.ts +1 -0
  830. package/src/cache/chrome-tags.ts +24 -0
  831. package/src/cli/commands/content.ts +31 -5
  832. package/src/cli/commands/export-seed.ts +2 -0
  833. package/src/client/index.ts +34 -7
  834. package/src/components/EmDashBodyEnd.astro +4 -1
  835. package/src/components/EmDashBodyStart.astro +4 -1
  836. package/src/components/EmDashHead.astro +35 -16
  837. package/src/components/EmDashImage.astro +2 -1
  838. package/src/components/EmDashMedia.astro +2 -1
  839. package/src/components/Gallery.astro +18 -4
  840. package/src/components/InlineEditor.astro +3 -1
  841. package/src/components/InlinePortableTextEditor.tsx +112 -12
  842. package/src/components/PortableText.astro +4 -0
  843. package/src/components/Table.astro +147 -62
  844. package/src/components/widgets/Archives.astro +2 -37
  845. package/src/config/errors.ts +13 -0
  846. package/src/content/converters/index.ts +2 -0
  847. package/src/content/converters/mark-safety.ts +1 -0
  848. package/src/content/converters/portable-text-identity.ts +139 -0
  849. package/src/content/converters/portable-text-to-prosemirror.ts +226 -69
  850. package/src/content/converters/prosemirror-to-portable-text.ts +128 -28
  851. package/src/content/converters/types.ts +11 -0
  852. package/src/database/dialect-helpers.ts +125 -0
  853. package/src/database/migrations/017_authorization_codes.ts +14 -4
  854. package/src/database/migrations/074_content_deleted_scheduled_index.ts +55 -0
  855. package/src/database/migrations/075_entry_edit_locks.ts +44 -0
  856. package/src/database/migrations/076_collection_nav_group.ts +22 -0
  857. package/src/database/migrations/077_plugin_storage_revisions.ts +89 -0
  858. package/src/database/migrations/runner.ts +9 -1
  859. package/src/database/repositories/byline.ts +32 -16
  860. package/src/database/repositories/content.ts +284 -48
  861. package/src/database/repositories/entry-locks.ts +257 -0
  862. package/src/database/repositories/media-usage.ts +66 -37
  863. package/src/database/repositories/media.ts +60 -1
  864. package/src/database/repositories/options.ts +69 -5
  865. package/src/database/repositories/plugin-storage.ts +233 -9
  866. package/src/database/repositories/redirect.ts +17 -6
  867. package/src/database/repositories/taxonomy.ts +8 -0
  868. package/src/database/repositories/types.ts +11 -7
  869. package/src/database/types.ts +15 -1
  870. package/src/db/adapters.ts +8 -1
  871. package/src/db/postgres.ts +23 -0
  872. package/src/emdash-runtime.ts +196 -199
  873. package/src/fields/boolean.ts +1 -1
  874. package/src/fields/datetime.ts +1 -1
  875. package/src/fields/file.ts +1 -1
  876. package/src/fields/integer.ts +1 -1
  877. package/src/fields/json.ts +1 -1
  878. package/src/fields/multiselect.ts +1 -1
  879. package/src/fields/number.ts +1 -1
  880. package/src/fields/portable-text.ts +1 -1
  881. package/src/fields/richtext.ts +1 -1
  882. package/src/fields/select.ts +1 -1
  883. package/src/fields/slug.ts +1 -1
  884. package/src/fields/text.ts +1 -1
  885. package/src/fields/textarea.ts +1 -1
  886. package/src/fields/types.ts +2 -2
  887. package/src/i18n/resolve.ts +44 -4
  888. package/src/index.ts +39 -3
  889. package/src/loader.ts +96 -12
  890. package/src/mcp/server.ts +251 -29
  891. package/src/media/image-endpoint.ts +5 -2
  892. package/src/media/normalize.ts +15 -4
  893. package/src/media/responsive.ts +23 -0
  894. package/src/media/usage/cleanup.ts +9 -8
  895. package/src/media/usage/content-refresh.ts +2 -1
  896. package/src/menus/index.ts +33 -21
  897. package/src/object-cache/index.ts +44 -2
  898. package/src/page/seo-contributions.ts +78 -1
  899. package/src/page/seo-panel.ts +47 -0
  900. package/src/plugin-types.ts +27 -1
  901. package/src/plugins/conditional-storage.ts +30 -0
  902. package/src/plugins/context.ts +49 -17
  903. package/src/plugins/hooks.ts +10 -1
  904. package/src/plugins/index.ts +15 -0
  905. package/src/plugins/manager.ts +6 -2
  906. package/src/plugins/manifest-schema.ts +1 -1
  907. package/src/plugins/routes.ts +3 -1
  908. package/src/plugins/sandbox/hook-result.ts +52 -0
  909. package/src/plugins/sandbox/index.ts +10 -0
  910. package/src/plugins/sandbox/types.ts +16 -2
  911. package/src/plugins/storage-query.ts +45 -0
  912. package/src/plugins/storage-update.ts +96 -0
  913. package/src/plugins/types.ts +120 -0
  914. package/src/query.ts +29 -3
  915. package/src/registry/artifact-checksum.ts +15 -0
  916. package/src/registry/artifact-verification.ts +85 -0
  917. package/src/registry/authoritative-records.ts +180 -0
  918. package/src/registry/config.ts +130 -27
  919. package/src/registry/record-labels.ts +28 -0
  920. package/src/registry/types.ts +5 -6
  921. package/src/schema/collection-slugs-cache.ts +157 -0
  922. package/src/schema/registry.ts +45 -3
  923. package/src/schema/types.ts +25 -6
  924. package/src/schema/zod-generator.ts +16 -13
  925. package/src/security/ssrf.ts +172 -143
  926. package/src/seed/apply.ts +4 -0
  927. package/src/seed/types.ts +7 -2
  928. package/src/seed/validate.ts +18 -1
  929. package/src/seo/hreflang.ts +1 -0
  930. package/src/seo/index.ts +5 -10
  931. package/src/seo/media-url.ts +26 -0
  932. package/src/settings/index.ts +16 -0
  933. package/src/storage/local.ts +1 -0
  934. package/src/storage/s3.ts +2 -0
  935. package/src/storage/types.ts +2 -0
  936. package/src/taxonomies/index.ts +40 -20
  937. package/src/taxonomies/term-counts.ts +22 -5
  938. package/src/widgets/archives.ts +67 -0
  939. package/src/widgets/index.ts +16 -0
  940. package/dist/api-B0w3uBiG.mjs +0 -2269
  941. package/dist/api-B0w3uBiG.mjs.map +0 -1
  942. package/dist/apply-B_VMpJ2P.mjs.map +0 -1
  943. package/dist/byline-Cpnwrje1.mjs.map +0 -1
  944. package/dist/challenge-store-D5pSY2S1.mjs.map +0 -1
  945. package/dist/content-CP-SeXNa.mjs.map +0 -1
  946. package/dist/content-refresh-DLOfIhYc.mjs.map +0 -1
  947. package/dist/context-BR5hRWiK.mjs.map +0 -1
  948. package/dist/dialect-helpers-BSbfaO3F.mjs.map +0 -1
  949. package/dist/errors-n2j49uNf.mjs.map +0 -1
  950. package/dist/index-DWbJrsAg.d.mts.map +0 -1
  951. package/dist/loader-ZsjE4paL.mjs.map +0 -1
  952. package/dist/manifest-schema-CUYPfCEH.mjs.map +0 -1
  953. package/dist/media-BjwNuLEu.mjs.map +0 -1
  954. package/dist/media-D6SeAfBl.mjs.map +0 -1
  955. package/dist/media-url-DH7giMx_.mjs.map +0 -1
  956. package/dist/menus-CNg0QlDt.mjs.map +0 -1
  957. package/dist/normalize-CSxdKBVa.mjs.map +0 -1
  958. package/dist/object-cache-71tPfV4A.mjs.map +0 -1
  959. package/dist/options-BHPeIbBd.mjs.map +0 -1
  960. package/dist/options-BhZIeFY1.d.mts.map +0 -1
  961. package/dist/query-D18vTdha.mjs.map +0 -1
  962. package/dist/redirect-CZkbozu9.mjs.map +0 -1
  963. package/dist/redirects-CRIM55KH.mjs.map +0 -1
  964. package/dist/registry-BiO5WwPq.mjs +0 -2716
  965. package/dist/registry-BiO5WwPq.mjs.map +0 -1
  966. package/dist/registry-Cx-jEJOS.mjs.map +0 -1
  967. package/dist/relations-BTPM2saV.mjs.map +0 -1
  968. package/dist/relations-C7B46bcU.d.mts.map +0 -1
  969. package/dist/resolve-DxUpTMmE.mjs.map +0 -1
  970. package/dist/runner-77GOZCnH.mjs.map +0 -1
  971. package/dist/runner-zgqA_CqP.d.mts.map +0 -1
  972. package/dist/seo-BSiTIXzd.mjs.map +0 -1
  973. package/dist/settings-BgN-0DT-.mjs.map +0 -1
  974. package/dist/snapshot-DSB8P7Lm.mjs.map +0 -1
  975. package/dist/ssrf-DqebVE34.mjs.map +0 -1
  976. package/dist/taxonomies-CPGxo-bn.mjs.map +0 -1
  977. package/dist/taxonomies-kmyudNGP.mjs.map +0 -1
  978. package/dist/types-75KIqzL9.mjs.map +0 -1
  979. package/dist/types-Br-ERaxh.d.mts.map +0 -1
  980. package/dist/types-D6OcIp2P.mjs +0 -3
  981. package/dist/types-DHxzBjbD.d.mts.map +0 -1
  982. package/dist/types-DJQxln7q.d.mts.map +0 -1
  983. package/dist/types-D_DWe7k9.d.mts.map +0 -1
  984. package/dist/types-Vg6f5i3s.d.mts.map +0 -1
  985. package/dist/validate-Dd-IUjnZ.mjs.map +0 -1
  986. package/dist/validate-DlqdOZXB.d.mts.map +0 -1
  987. package/dist/version-ri-sCyAp.mjs +0 -7
  988. package/dist/widgets-CpmZd9Q-.mjs.map +0 -1
  989. package/dist/zod-generator-DylzusHh.mjs.map +0 -1
  990. /package/dist/{api-tokens-B5NHLNUN.mjs → api-tokens-B_lfpmH3.mjs} +0 -0
  991. /package/dist/{ssrf-BPKzkm6O.mjs → ssrf-TQhaljuI.mjs} +0 -0
@@ -1,2716 +0,0 @@
1
- import { i as validateStorageCollectionName, n as validateJsonFieldName, r as validatePluginIdentifier } from "./validate-BpwoDOgt.mjs";
2
- import { l as jsonExtractExpr, s as isPostgres } from "./dialect-helpers-BSbfaO3F.mjs";
3
- import { t as PluginStateRepository } from "./state-BJfU9Uzw.mjs";
4
- import { a as reconcileManifestAccess, i as pluginManifestSchema, r as normalizeManifestRoute } from "./manifest-schema-CUYPfCEH.mjs";
5
- import { a as normalizeCapabilities, r as declaredAccessToCapabilities } from "./types-D6OcIp2P.mjs";
6
- import { t as EmDashStorageError } from "./types-BXUmsINL.mjs";
7
- import { r as resolveAndValidateExternalUrlTarget, t as SsrfError } from "./ssrf-DqebVE34.mjs";
8
- import { sql } from "kysely";
9
- import { createGzipDecoder, unpackTar } from "modern-tar";
10
- import { ClientResponseError, ClientValidationError } from "@atcute/client";
11
- import { checkEnvCompatibility, findSkippedEnvConstraints } from "@emdash-cms/registry-client/env";
12
- import { evaluateRegistryReleaseWithdrawal } from "@emdash-cms/registry-client/withdrawal";
13
- import { NSID } from "@emdash-cms/registry-lexicons";
14
- import { fetchReleaseArtifact } from "@emdash-cms/registry-verification/artifact";
15
- import { isDid } from "@atcute/lexicons/syntax";
16
-
17
- //#region src/plugins/marketplace.ts
18
- /**
19
- * MarketplaceClient — HTTP client for the EmDash Plugin Marketplace
20
- *
21
- * Used by the install/update/proxy endpoints in EmDash core to communicate
22
- * with the marketplace Worker. The marketplace is a distribution channel,
23
- * not a runtime dependency — bundles are copied to site-local R2 at install time.
24
- */
25
- const TRAILING_SLASHES$1 = /\/+$/;
26
- const LEADING_DOT_SLASH = /^\.\//;
27
- var MarketplaceError = class extends Error {
28
- constructor(message, status, code) {
29
- super(message);
30
- this.status = status;
31
- this.code = code;
32
- this.name = "MarketplaceError";
33
- }
34
- };
35
- var MarketplaceUnavailableError = class extends MarketplaceError {
36
- constructor(cause) {
37
- super("Plugin marketplace is unavailable", void 0, "MARKETPLACE_UNAVAILABLE");
38
- if (cause) this.cause = cause;
39
- }
40
- };
41
- var MarketplaceClientImpl = class {
42
- baseUrl;
43
- siteOrigin;
44
- constructor(baseUrl, siteOrigin) {
45
- this.baseUrl = baseUrl.replace(TRAILING_SLASHES$1, "");
46
- this.siteOrigin = siteOrigin;
47
- }
48
- async search(query, opts) {
49
- const params = new URLSearchParams();
50
- if (query) params.set("q", query);
51
- if (opts?.category) params.set("category", opts.category);
52
- if (opts?.capability) params.set("capability", opts.capability);
53
- if (opts?.sort) params.set("sort", opts.sort);
54
- if (opts?.cursor) params.set("cursor", opts.cursor);
55
- if (opts?.limit) params.set("limit", String(opts.limit));
56
- const qs = params.toString();
57
- const url = `${this.baseUrl}/api/v1/plugins${qs ? `?${qs}` : ""}`;
58
- return await this.fetchJson(url);
59
- }
60
- async getPlugin(id) {
61
- const url = `${this.baseUrl}/api/v1/plugins/${encodeURIComponent(id)}`;
62
- return this.fetchJson(url);
63
- }
64
- async getVersions(id) {
65
- const url = `${this.baseUrl}/api/v1/plugins/${encodeURIComponent(id)}/versions`;
66
- return (await this.fetchJson(url)).items;
67
- }
68
- async downloadBundle(id, version) {
69
- const bundleUrl = `${this.baseUrl}/api/v1/plugins/${encodeURIComponent(id)}/versions/${encodeURIComponent(version)}/bundle`;
70
- const marketplaceOrigin = new URL(this.baseUrl).origin;
71
- const MAX_REDIRECTS = 5;
72
- let response;
73
- try {
74
- let currentUrl = bundleUrl;
75
- response = await fetch(currentUrl, { redirect: "manual" });
76
- for (let i = 0; i < MAX_REDIRECTS; i++) {
77
- if (response.status < 300 || response.status >= 400) break;
78
- const location = response.headers.get("location");
79
- if (!location) break;
80
- const target = new URL(location, currentUrl);
81
- if (target.origin !== marketplaceOrigin) throw new MarketplaceError(`Bundle download redirected to untrusted host: ${target.origin}`, response.status, "BUNDLE_REDIRECT_UNTRUSTED");
82
- currentUrl = target.href;
83
- response = await fetch(currentUrl, { redirect: "manual" });
84
- }
85
- if (response.status >= 300 && response.status < 400) throw new MarketplaceError(`Bundle download exceeded maximum redirects (${MAX_REDIRECTS})`, response.status, "BUNDLE_TOO_MANY_REDIRECTS");
86
- } catch (err) {
87
- if (err instanceof MarketplaceError) throw err;
88
- throw new MarketplaceUnavailableError(err);
89
- }
90
- if (!response.ok) throw new MarketplaceError(`Failed to download bundle: ${response.status} ${response.statusText}`, response.status, "BUNDLE_DOWNLOAD_FAILED");
91
- const tarballBytes = new Uint8Array(await response.arrayBuffer());
92
- try {
93
- return await extractBundle(tarballBytes);
94
- } catch (err) {
95
- if (err instanceof MarketplaceError) throw err;
96
- throw new MarketplaceError("Failed to extract plugin bundle", void 0, "BUNDLE_EXTRACT_FAILED");
97
- }
98
- }
99
- async reportInstall(id, version) {
100
- const siteHash = await generateSiteHash(this.siteOrigin);
101
- const url = `${this.baseUrl}/api/v1/plugins/${encodeURIComponent(id)}/installs`;
102
- try {
103
- await fetch(url, {
104
- method: "POST",
105
- headers: { "Content-Type": "application/json" },
106
- body: JSON.stringify({
107
- siteHash,
108
- version
109
- })
110
- });
111
- } catch {}
112
- }
113
- async searchThemes(query, opts) {
114
- const params = new URLSearchParams();
115
- if (query) params.set("q", query);
116
- if (opts?.keyword) params.set("keyword", opts.keyword);
117
- if (opts?.sort) params.set("sort", opts.sort);
118
- if (opts?.cursor) params.set("cursor", opts.cursor);
119
- if (opts?.limit) params.set("limit", String(opts.limit));
120
- const qs = params.toString();
121
- const url = `${this.baseUrl}/api/v1/themes${qs ? `?${qs}` : ""}`;
122
- return this.fetchJson(url);
123
- }
124
- async getTheme(id) {
125
- const url = `${this.baseUrl}/api/v1/themes/${encodeURIComponent(id)}`;
126
- return this.fetchJson(url);
127
- }
128
- async fetchJson(url) {
129
- let response;
130
- try {
131
- response = await fetch(url, { headers: { Accept: "application/json" } });
132
- } catch (err) {
133
- throw new MarketplaceUnavailableError(err);
134
- }
135
- if (!response.ok) {
136
- let errorMessage = `Marketplace request failed: ${response.status}`;
137
- try {
138
- const body = await response.json();
139
- if (body.error) errorMessage = body.error;
140
- } catch {}
141
- throw new MarketplaceError(errorMessage, response.status);
142
- }
143
- return await response.json();
144
- }
145
- };
146
- /**
147
- * Extract manifest + code files from a tarball.
148
- *
149
- * The tarball is a gzipped tar archive containing:
150
- * - manifest.json
151
- * - backend.js
152
- * - admin.js (optional)
153
- *
154
- * We use a minimal tar parser since we only need to read a few small files.
155
- */
156
- /**
157
- * Exported so the experimental registry install handler can reuse the
158
- * same parse / validate / hash primitive. Despite the file name, this
159
- * function predates the marketplace-vs-registry split and is generic
160
- * over plugin bundle tarballs regardless of distribution channel.
161
- */
162
- const MAX_DECOMPRESSED_BUNDLE_BYTES = 256 * 1024;
163
- const MAX_BUNDLE_TAR_ENTRIES = 32;
164
- async function extractBundle(tarballBytes) {
165
- const reader = new ReadableStream({ start(controller) {
166
- controller.enqueue(tarballBytes);
167
- controller.close();
168
- } }).pipeThrough(createGzipDecoder()).getReader();
169
- const chunks = [];
170
- let total = 0;
171
- while (true) {
172
- const { done, value } = await reader.read();
173
- if (done) break;
174
- if (!value) continue;
175
- total += value.byteLength;
176
- if (total > MAX_DECOMPRESSED_BUNDLE_BYTES) {
177
- try {
178
- await reader.cancel();
179
- } catch {}
180
- throw new MarketplaceError(`Bundle decompressed size exceeds limit (${MAX_DECOMPRESSED_BUNDLE_BYTES} bytes)`, void 0, "INVALID_BUNDLE");
181
- }
182
- chunks.push(value);
183
- }
184
- const decompressedBytes = new Uint8Array(total);
185
- {
186
- let offset = 0;
187
- for (const chunk of chunks) {
188
- decompressedBytes.set(chunk, offset);
189
- offset += chunk.byteLength;
190
- }
191
- }
192
- const entries = await unpackTar(new ReadableStream({ start(controller) {
193
- controller.enqueue(decompressedBytes);
194
- controller.close();
195
- } }));
196
- if (entries.length > MAX_BUNDLE_TAR_ENTRIES) throw new MarketplaceError(`Bundle has too many tar entries (${entries.length} > ${MAX_BUNDLE_TAR_ENTRIES})`, void 0, "INVALID_BUNDLE");
197
- const decoder = new TextDecoder();
198
- const files = /* @__PURE__ */ new Map();
199
- for (const entry of entries) if (entry.data && entry.header.type === "file") {
200
- const name = entry.header.name.replace(LEADING_DOT_SLASH, "");
201
- files.set(name, decoder.decode(entry.data));
202
- }
203
- const manifestJson = files.get("manifest.json");
204
- const backendCode = files.get("backend.js");
205
- if (!manifestJson) throw new MarketplaceError("Invalid bundle: missing manifest.json", void 0, "INVALID_BUNDLE");
206
- if (!backendCode) throw new MarketplaceError("Invalid bundle: missing backend.js", void 0, "INVALID_BUNDLE");
207
- let manifest;
208
- try {
209
- const parsed = JSON.parse(manifestJson);
210
- const result = pluginManifestSchema.safeParse(parsed);
211
- if (!result.success) throw new MarketplaceError("Invalid bundle: manifest.json failed validation", void 0, "INVALID_BUNDLE");
212
- manifest = reconcileManifestAccess(result.data);
213
- } catch (err) {
214
- if (err instanceof MarketplaceError) throw err;
215
- throw new MarketplaceError("Invalid bundle: malformed manifest.json", void 0, "INVALID_BUNDLE");
216
- }
217
- const hashBuffer = await crypto.subtle.digest("SHA-256", tarballBytes);
218
- const hashArray = new Uint8Array(hashBuffer);
219
- const checksum = Array.from(hashArray, (b) => b.toString(16).padStart(2, "0")).join("");
220
- return {
221
- manifest,
222
- backendCode,
223
- adminCode: files.get("admin.js"),
224
- checksum
225
- };
226
- }
227
- /**
228
- * Generate a stable non-identifying site hash from the site origin.
229
- * The same origin always produces the same hash, so the marketplace
230
- * installs table deduplicates correctly per (plugin_id, site_hash).
231
- */
232
- async function generateSiteHash(siteOrigin) {
233
- const seed = siteOrigin ? `emdash-site:${siteOrigin}` : `emdash-anonymous`;
234
- try {
235
- const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(seed));
236
- const arr = new Uint8Array(hash);
237
- return Array.from(arr.slice(0, 8), (b) => b.toString(16).padStart(2, "0")).join("");
238
- } catch {
239
- let h = 2166136261;
240
- for (let i = 0; i < seed.length; i++) {
241
- h ^= seed.charCodeAt(i);
242
- h = Math.imul(h, 16777619);
243
- }
244
- const h2 = h ^ h >>> 16;
245
- return (h >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0");
246
- }
247
- }
248
- /**
249
- * Create a MarketplaceClient for the given marketplace URL.
250
- *
251
- * @param baseUrl - The marketplace API base URL (e.g. "https://marketplace.emdashcms.com")
252
- * @param siteOrigin - The origin of the EmDash site (e.g. "https://myblog.example.com").
253
- * Used to generate a stable, non-identifying site hash for install deduplication.
254
- */
255
- function createMarketplaceClient(baseUrl, siteOrigin) {
256
- return new MarketplaceClientImpl(baseUrl, siteOrigin);
257
- }
258
-
259
- //#endregion
260
- //#region src/plugins/storage-indexes.ts
261
- /**
262
- * Generate a deterministic index name.
263
- * Unique indexes use a `uidx_` prefix to avoid collisions with regular indexes on the same fields.
264
- */
265
- function generateIndexName(pluginId, collection, fields, options) {
266
- return `${options?.unique ? "uidx" : "idx"}_plugin_${pluginId}_${collection}_${fields.join("_")}`.substring(0, 128);
267
- }
268
- /**
269
- * Generate a Kysely sql expression for creating an expression index.
270
- *
271
- * Validates all inputs before interpolation. The collection uses the
272
- * permissive manifest-key rules rather than SQL-identifier rules — it is
273
- * stored as opaque text and only reaches SQL inside the generated index
274
- * name — so kebab-case collections index like any other.
275
- */
276
- function generateCreateIndexSql(db, pluginId, collection, fields, options) {
277
- validatePluginIdentifier(pluginId, "plugin ID");
278
- validateStorageCollectionName(collection, "collection name");
279
- for (const field of fields) validateJsonFieldName(field, "index field name");
280
- const indexName = generateIndexName(pluginId, collection, fields, options);
281
- const expressions = fields.map((field) => {
282
- if (isPostgres(db)) return `(${jsonExtractExpr(db, "data", field)})`;
283
- return jsonExtractExpr(db, "data", field);
284
- }).join(", ");
285
- const createKeyword = options?.unique ? "CREATE UNIQUE INDEX" : "CREATE INDEX";
286
- return sql`${sql.raw(createKeyword)} IF NOT EXISTS ${sql.ref(indexName)}
287
- ON _plugin_storage(plugin_id, collection, ${sql.raw(expressions)})
288
- `;
289
- }
290
- /**
291
- * Generate a Kysely sql expression for dropping an index.
292
- *
293
- * Uses sql.ref() for safe identifier quoting.
294
- */
295
- function generateDropIndexSql(indexName) {
296
- return sql`DROP INDEX IF EXISTS ${sql.ref(indexName)}`;
297
- }
298
- /**
299
- * Normalize index declarations to arrays of field arrays
300
- */
301
- function normalizeIndexes(indexes) {
302
- return indexes.map((index) => Array.isArray(index) ? index : [index]);
303
- }
304
- /**
305
- * Create all declared indexes for a plugin collection
306
- */
307
- async function createStorageIndexes(db, pluginId, collection, indexes, options) {
308
- const normalized = normalizeIndexes(indexes);
309
- const uniqueNormalized = options?.uniqueIndexes ? normalizeIndexes(options.uniqueIndexes) : [];
310
- const uniqueSet = new Set(uniqueNormalized.map((f) => f.join(",")));
311
- const allEntries = [...normalized.filter((f) => !uniqueSet.has(f.join(","))).map((fields) => ({
312
- fields,
313
- unique: false
314
- })), ...uniqueNormalized.map((fields) => ({
315
- fields,
316
- unique: true
317
- }))];
318
- const created = [];
319
- const errors = [];
320
- for (const entry of allEntries) {
321
- const { fields } = entry;
322
- const indexName = generateIndexName(pluginId, collection, fields, { unique: entry.unique });
323
- try {
324
- await generateCreateIndexSql(db, pluginId, collection, fields, { unique: entry.unique }).execute(db);
325
- await db.insertInto("_plugin_indexes").values({
326
- plugin_id: pluginId,
327
- collection,
328
- index_name: indexName,
329
- fields: JSON.stringify(fields)
330
- }).onConflict((oc) => oc.columns([
331
- "plugin_id",
332
- "collection",
333
- "index_name"
334
- ]).doUpdateSet({ fields: JSON.stringify(fields) })).execute();
335
- created.push(indexName);
336
- } catch (error) {
337
- errors.push({
338
- index: indexName,
339
- error: error instanceof Error ? error.message : String(error)
340
- });
341
- }
342
- }
343
- return {
344
- created,
345
- errors
346
- };
347
- }
348
- /**
349
- * Remove indexes that are no longer declared
350
- */
351
- async function removeOrphanedIndexes(db, pluginId, collection, currentIndexes, options) {
352
- const normalized = normalizeIndexes(currentIndexes);
353
- const uniqueNormalized = options?.uniqueIndexes ? normalizeIndexes(options.uniqueIndexes) : [];
354
- const uniqueSet = new Set(uniqueNormalized.map((f) => f.join(",")));
355
- const currentIndexNames = /* @__PURE__ */ new Set();
356
- for (const fields of normalized) if (!uniqueSet.has(fields.join(","))) currentIndexNames.add(generateIndexName(pluginId, collection, fields));
357
- for (const fields of uniqueNormalized) currentIndexNames.add(generateIndexName(pluginId, collection, fields, { unique: true }));
358
- const existingIndexes = await db.selectFrom("_plugin_indexes").select(["index_name"]).where("plugin_id", "=", pluginId).where("collection", "=", collection).execute();
359
- const removed = [];
360
- const errors = [];
361
- for (const { index_name } of existingIndexes) if (!currentIndexNames.has(index_name)) try {
362
- await generateDropIndexSql(index_name).execute(db);
363
- await db.deleteFrom("_plugin_indexes").where("plugin_id", "=", pluginId).where("collection", "=", collection).where("index_name", "=", index_name).execute();
364
- removed.push(index_name);
365
- } catch (error) {
366
- errors.push({
367
- index: index_name,
368
- error: error instanceof Error ? error.message : String(error)
369
- });
370
- }
371
- return {
372
- removed,
373
- errors
374
- };
375
- }
376
- /**
377
- * Sync indexes for a plugin collection (create new, remove old)
378
- */
379
- async function syncStorageIndexes(db, pluginId, collection, indexes, options) {
380
- const [createResult, removeResult] = await Promise.all([createStorageIndexes(db, pluginId, collection, indexes, options), removeOrphanedIndexes(db, pluginId, collection, indexes, options)]);
381
- return {
382
- created: createResult.created,
383
- removed: removeResult.removed,
384
- errors: [...createResult.errors, ...removeResult.errors]
385
- };
386
- }
387
- /**
388
- * Materialize the storage indexes a set of plugins declare in their
389
- * manifests. Failures are logged per collection and never thrown — a missing
390
- * index affects query performance, not correctness, so it must not fail an
391
- * install or a scheduler tick.
392
- */
393
- async function syncDeclaredStorageIndexes(db, plugins) {
394
- for (const plugin of plugins) for (const [collection, config] of Object.entries(plugin.storage ?? {})) try {
395
- const result = await syncStorageIndexes(db, plugin.id, collection, config.indexes, { uniqueIndexes: config.uniqueIndexes });
396
- for (const failure of result.errors) console.error(`[plugins] Failed to sync storage index ${failure.index} for ${plugin.id}/${collection}: ${failure.error}`);
397
- } catch (error) {
398
- console.error(`[plugins] Failed to sync storage indexes for ${plugin.id}/${collection}:`, error);
399
- }
400
- }
401
- /**
402
- * Remove all indexes for a plugin
403
- */
404
- async function removeAllPluginIndexes(db, pluginId) {
405
- const existingIndexes = await db.selectFrom("_plugin_indexes").select(["index_name", "collection"]).where("plugin_id", "=", pluginId).execute();
406
- const removed = [];
407
- const errors = [];
408
- for (const { index_name } of existingIndexes) try {
409
- await generateDropIndexSql(index_name).execute(db);
410
- removed.push(index_name);
411
- } catch (error) {
412
- errors.push({
413
- index: index_name,
414
- error: error instanceof Error ? error.message : String(error)
415
- });
416
- }
417
- await db.deleteFrom("_plugin_indexes").where("plugin_id", "=", pluginId).execute();
418
- return {
419
- removed,
420
- errors
421
- };
422
- }
423
-
424
- //#endregion
425
- //#region src/api/handlers/marketplace.ts
426
- /** Semver-like pattern: digits, dots, hyphens, plus signs (e.g. 1.0.0, 1.0.0-beta.1) */
427
- const VERSION_PATTERN = /^[a-z0-9][a-z0-9._+-]*$/i;
428
- function validateVersion(version) {
429
- if (version.includes("..")) throw new Error("Invalid version format");
430
- if (!VERSION_PATTERN.test(version)) throw new Error("Invalid version format");
431
- }
432
- function getClient(marketplaceUrl, siteOrigin) {
433
- if (!marketplaceUrl) return null;
434
- return createMarketplaceClient(marketplaceUrl, siteOrigin);
435
- }
436
- function diffCapabilities(oldCaps, newCaps) {
437
- const oldNorm = normalizeCapabilities(oldCaps);
438
- const newNorm = normalizeCapabilities(newCaps);
439
- const oldSet = new Set(oldNorm);
440
- const newSet = new Set(newNorm);
441
- return {
442
- added: newNorm.filter((c) => !oldSet.has(c)),
443
- removed: oldNorm.filter((c) => !newSet.has(c))
444
- };
445
- }
446
- /**
447
- * Diff route visibility between two manifests.
448
- * Returns routes that changed from private to public (newly exposed).
449
- */
450
- function diffRouteVisibility(oldManifest, newManifest) {
451
- const oldPublicRoutes = /* @__PURE__ */ new Set();
452
- if (oldManifest) for (const entry of oldManifest.routes) {
453
- const normalized = normalizeManifestRoute(entry);
454
- if (normalized.public === true) oldPublicRoutes.add(normalized.name);
455
- }
456
- const newlyPublic = [];
457
- for (const entry of newManifest.routes) {
458
- const normalized = normalizeManifestRoute(entry);
459
- if (normalized.public === true && !oldPublicRoutes.has(normalized.name)) newlyPublic.push(normalized.name);
460
- }
461
- return { newlyPublic };
462
- }
463
- async function resolveVersionMetadata(client, pluginId, pluginDetail, version) {
464
- if (pluginDetail.latestVersion?.version === version) return {
465
- version: pluginDetail.latestVersion.version,
466
- minEmDashVersion: pluginDetail.latestVersion.minEmDashVersion,
467
- bundleSize: pluginDetail.latestVersion.bundleSize,
468
- checksum: pluginDetail.latestVersion.checksum,
469
- changelog: pluginDetail.latestVersion.changelog,
470
- capabilities: pluginDetail.latestVersion.capabilities,
471
- status: pluginDetail.latestVersion.status,
472
- auditVerdict: pluginDetail.latestVersion.audit?.verdict ?? null,
473
- imageAuditVerdict: pluginDetail.latestVersion.imageAudit?.verdict ?? null,
474
- publishedAt: pluginDetail.latestVersion.publishedAt
475
- };
476
- return (await client.getVersions(pluginId)).find((v) => v.version === version) ?? null;
477
- }
478
- function validateBundleIdentity(bundle, pluginId, version) {
479
- if (bundle.manifest.id !== pluginId) return {
480
- success: false,
481
- error: {
482
- code: "MANIFEST_MISMATCH",
483
- message: `Bundle manifest ID (${bundle.manifest.id}) does not match requested plugin (${pluginId})`
484
- }
485
- };
486
- if (bundle.manifest.version !== version) return {
487
- success: false,
488
- error: {
489
- code: "MANIFEST_VERSION_MISMATCH",
490
- message: `Bundle manifest version (${bundle.manifest.version}) does not match requested version (${version})`
491
- }
492
- };
493
- return null;
494
- }
495
- function bundlePrefix(source, pluginId, version) {
496
- return `${source}/${pluginId}/${version}`;
497
- }
498
- async function storeBundleInR2(storage, pluginId, version, bundle, source = "marketplace") {
499
- validatePluginIdentifier(pluginId, "plugin ID");
500
- validateVersion(version);
501
- const prefix = bundlePrefix(source, pluginId, version);
502
- await storage.upload({
503
- key: `${prefix}/manifest.json`,
504
- body: new TextEncoder().encode(JSON.stringify(bundle.manifest)),
505
- contentType: "application/json"
506
- });
507
- await storage.upload({
508
- key: `${prefix}/backend.js`,
509
- body: new TextEncoder().encode(bundle.backendCode),
510
- contentType: "application/javascript"
511
- });
512
- if (bundle.adminCode) await storage.upload({
513
- key: `${prefix}/admin.js`,
514
- body: new TextEncoder().encode(bundle.adminCode),
515
- contentType: "application/javascript"
516
- });
517
- }
518
- /** Read a ReadableStream to string */
519
- async function streamToText(stream) {
520
- return new Response(stream).text();
521
- }
522
- /**
523
- * Load a plugin bundle from site-local R2 storage.
524
- *
525
- * `source` selects the R2 key prefix: marketplace plugins are stored
526
- * under `marketplace/<id>/<version>/`, registry plugins under
527
- * `registry/<id>/<version>/`. Defaults to `"marketplace"` for
528
- * backwards compatibility with pre-registry call sites.
529
- */
530
- async function loadBundleFromR2(storage, pluginId, version, source = "marketplace") {
531
- validatePluginIdentifier(pluginId, "plugin ID");
532
- validateVersion(version);
533
- const prefix = bundlePrefix(source, pluginId, version);
534
- try {
535
- const manifestResult = await storage.download(`${prefix}/manifest.json`);
536
- const backendResult = await storage.download(`${prefix}/backend.js`);
537
- const manifestText = await streamToText(manifestResult.body);
538
- const backendCode = await streamToText(backendResult.body);
539
- const parsed = JSON.parse(manifestText);
540
- const result = pluginManifestSchema.safeParse(parsed);
541
- if (!result.success) return null;
542
- const manifest = reconcileManifestAccess(result.data);
543
- let adminCode;
544
- try {
545
- adminCode = await streamToText((await storage.download(`${prefix}/admin.js`)).body);
546
- } catch {}
547
- return {
548
- manifest,
549
- backendCode,
550
- adminCode
551
- };
552
- } catch {
553
- return null;
554
- }
555
- }
556
- /** Delete a plugin bundle from site-local R2 storage */
557
- async function deleteBundleFromR2(storage, pluginId, version, source = "marketplace") {
558
- validatePluginIdentifier(pluginId, "plugin ID");
559
- validateVersion(version);
560
- const prefix = bundlePrefix(source, pluginId, version);
561
- for (const file of [
562
- "manifest.json",
563
- "backend.js",
564
- "admin.js"
565
- ]) try {
566
- await storage.delete(`${prefix}/${file}`);
567
- } catch {}
568
- }
569
- async function handleMarketplaceInstall(db, storage, sandboxRunner, marketplaceUrl, pluginId, opts) {
570
- const client = getClient(marketplaceUrl, opts?.siteOrigin);
571
- if (!client) return {
572
- success: false,
573
- error: {
574
- code: "MARKETPLACE_NOT_CONFIGURED",
575
- message: "Marketplace is not configured"
576
- }
577
- };
578
- if (!storage) return {
579
- success: false,
580
- error: {
581
- code: "STORAGE_NOT_CONFIGURED",
582
- message: "Storage is required for marketplace plugin installation"
583
- }
584
- };
585
- if (!opts?.sandboxBypassed && (!sandboxRunner || !sandboxRunner.isAvailable())) return {
586
- success: false,
587
- error: {
588
- code: "SANDBOX_NOT_AVAILABLE",
589
- message: "Sandbox runner is required for marketplace plugins"
590
- }
591
- };
592
- try {
593
- const stateRepo = new PluginStateRepository(db);
594
- const existing = await stateRepo.get(pluginId);
595
- if (existing && existing.source === "marketplace") return {
596
- success: false,
597
- error: {
598
- code: "ALREADY_INSTALLED",
599
- message: `Plugin ${pluginId} is already installed`
600
- }
601
- };
602
- if (opts?.configuredPluginIds?.has(pluginId)) return {
603
- success: false,
604
- error: {
605
- code: "PLUGIN_ID_CONFLICT",
606
- message: `Cannot install marketplace plugin "${pluginId}" — a configured plugin with the same ID already exists`
607
- }
608
- };
609
- const pluginDetail = await client.getPlugin(pluginId);
610
- const version = opts?.version ?? pluginDetail.latestVersion?.version;
611
- if (!version) return {
612
- success: false,
613
- error: {
614
- code: "NO_VERSION",
615
- message: `No published versions found for plugin ${pluginId}`
616
- }
617
- };
618
- const versionMetadata = await resolveVersionMetadata(client, pluginId, pluginDetail, version);
619
- if (!versionMetadata) return {
620
- success: false,
621
- error: {
622
- code: "NO_VERSION",
623
- message: `Version ${version} was not found for plugin ${pluginId}`
624
- }
625
- };
626
- if (versionMetadata.auditVerdict === "fail" || versionMetadata.auditVerdict === "warn") return {
627
- success: false,
628
- error: {
629
- code: "AUDIT_FAILED",
630
- message: versionMetadata.auditVerdict === "fail" ? "Plugin failed security audit and cannot be installed" : "Plugin audit was inconclusive and cannot be installed until reviewed"
631
- }
632
- };
633
- const bundle = await client.downloadBundle(pluginId, version);
634
- if (versionMetadata.checksum && bundle.checksum !== versionMetadata.checksum) return {
635
- success: false,
636
- error: {
637
- code: "CHECKSUM_MISMATCH",
638
- message: "Bundle checksum does not match marketplace record. Download may be corrupted."
639
- }
640
- };
641
- const bundleIdentityError = validateBundleIdentity(bundle, pluginId, version);
642
- if (bundleIdentityError) return bundleIdentityError;
643
- if ((bundle.manifest.mcp?.tools.length ?? 0) > 0 && !opts?.confirmMcpTools) return {
644
- success: false,
645
- error: {
646
- code: "MCP_TOOL_CONSENT_REQUIRED",
647
- message: "Plugin MCP tools require explicit consent",
648
- details: { mcpTools: bundle.manifest.mcp?.tools.map(({ inputSchema: _, outputSchema: __, ...tool }) => tool) }
649
- }
650
- };
651
- await storeBundleInR2(storage, pluginId, version, bundle);
652
- await stateRepo.upsert(pluginId, version, "active", {
653
- source: "marketplace",
654
- marketplaceVersion: version,
655
- displayName: pluginDetail.name,
656
- description: pluginDetail.description ?? void 0
657
- });
658
- await syncDeclaredStorageIndexes(db, [bundle.manifest]);
659
- client.reportInstall(pluginId, version).catch(() => {});
660
- return {
661
- success: true,
662
- data: {
663
- pluginId,
664
- version,
665
- capabilities: bundle.manifest.capabilities
666
- }
667
- };
668
- } catch (err) {
669
- if (err instanceof MarketplaceUnavailableError) return {
670
- success: false,
671
- error: {
672
- code: "MARKETPLACE_UNAVAILABLE",
673
- message: "Plugin marketplace is currently unavailable"
674
- }
675
- };
676
- if (err instanceof MarketplaceError) return {
677
- success: false,
678
- error: {
679
- code: err.code ?? "MARKETPLACE_ERROR",
680
- message: err.message
681
- }
682
- };
683
- if (err instanceof EmDashStorageError) return {
684
- success: false,
685
- error: {
686
- code: err.code ?? "STORAGE_ERROR",
687
- message: "Storage error while installing plugin"
688
- }
689
- };
690
- if (err && typeof err === "object" && "code" in err) {
691
- const code = err.code;
692
- if (typeof code === "string" && code.trim()) return {
693
- success: false,
694
- error: {
695
- code,
696
- message: "Failed to install plugin from marketplace"
697
- }
698
- };
699
- }
700
- console.error("Failed to install marketplace plugin:", err);
701
- return {
702
- success: false,
703
- error: {
704
- code: "INSTALL_FAILED",
705
- message: "Failed to install plugin from marketplace"
706
- }
707
- };
708
- }
709
- }
710
- async function handleMarketplaceUpdate(db, storage, sandboxRunner, marketplaceUrl, pluginId, opts) {
711
- const client = getClient(marketplaceUrl);
712
- if (!client) return {
713
- success: false,
714
- error: {
715
- code: "MARKETPLACE_NOT_CONFIGURED",
716
- message: "Marketplace is not configured"
717
- }
718
- };
719
- if (!storage) return {
720
- success: false,
721
- error: {
722
- code: "STORAGE_NOT_CONFIGURED",
723
- message: "Storage is required"
724
- }
725
- };
726
- if (!opts?.sandboxBypassed && (!sandboxRunner || !sandboxRunner.isAvailable())) return {
727
- success: false,
728
- error: {
729
- code: "SANDBOX_NOT_AVAILABLE",
730
- message: "Sandbox runner is required"
731
- }
732
- };
733
- try {
734
- const stateRepo = new PluginStateRepository(db);
735
- const existing = await stateRepo.get(pluginId);
736
- if (!existing || existing.source !== "marketplace") return {
737
- success: false,
738
- error: {
739
- code: "NOT_FOUND",
740
- message: `No marketplace plugin found: ${pluginId}`
741
- }
742
- };
743
- const oldVersion = existing.marketplaceVersion ?? existing.version;
744
- const pluginDetail = await client.getPlugin(pluginId);
745
- const newVersion = opts?.version ?? pluginDetail.latestVersion?.version;
746
- if (!newVersion) return {
747
- success: false,
748
- error: {
749
- code: "NO_VERSION",
750
- message: "No newer version available"
751
- }
752
- };
753
- if (newVersion === oldVersion) return {
754
- success: false,
755
- error: {
756
- code: "ALREADY_UP_TO_DATE",
757
- message: "Plugin is already up to date"
758
- }
759
- };
760
- const versionMetadata = await resolveVersionMetadata(client, pluginId, pluginDetail, newVersion);
761
- if (!versionMetadata) return {
762
- success: false,
763
- error: {
764
- code: "NO_VERSION",
765
- message: `Version ${newVersion} was not found for plugin ${pluginId}`
766
- }
767
- };
768
- const bundle = await client.downloadBundle(pluginId, newVersion);
769
- if (versionMetadata.checksum && bundle.checksum !== versionMetadata.checksum) return {
770
- success: false,
771
- error: {
772
- code: "CHECKSUM_MISMATCH",
773
- message: "Bundle checksum does not match marketplace record. Download may be corrupted."
774
- }
775
- };
776
- const bundleIdentityError = validateBundleIdentity(bundle, pluginId, newVersion);
777
- if (bundleIdentityError) return bundleIdentityError;
778
- const oldBundle = await loadBundleFromR2(storage, pluginId, oldVersion);
779
- const capabilityChanges = diffCapabilities(oldBundle?.manifest.capabilities ?? [], bundle.manifest.capabilities);
780
- if (capabilityChanges.added.length > 0 && !opts?.confirmCapabilityChanges) return {
781
- success: false,
782
- error: {
783
- code: "CAPABILITY_ESCALATION",
784
- message: "Plugin update requires new capabilities",
785
- details: { capabilityChanges }
786
- }
787
- };
788
- const routeVisibilityChanges = diffRouteVisibility(oldBundle?.manifest, bundle.manifest);
789
- const hasNewPublicRoutes = routeVisibilityChanges.newlyPublic.length > 0;
790
- if (hasNewPublicRoutes && !opts?.confirmRouteVisibilityChanges) return {
791
- success: false,
792
- error: {
793
- code: "ROUTE_VISIBILITY_ESCALATION",
794
- message: "Plugin update exposes new public (unauthenticated) routes",
795
- details: {
796
- routeVisibilityChanges,
797
- capabilityChanges
798
- }
799
- }
800
- };
801
- const oldMcpTools = [...oldBundle?.manifest.mcp?.tools ?? []].toSorted((a, b) => a.name.localeCompare(b.name));
802
- const newMcpTools = [...bundle.manifest.mcp?.tools ?? []].toSorted((a, b) => a.name.localeCompare(b.name));
803
- if (JSON.stringify(oldMcpTools) !== JSON.stringify(newMcpTools) && !opts?.confirmMcpTools) return {
804
- success: false,
805
- error: {
806
- code: "MCP_TOOL_CONSENT_REQUIRED",
807
- message: "Plugin update changes its MCP tools",
808
- details: { mcpTools: newMcpTools.map(({ inputSchema: _, outputSchema: __, ...tool }) => tool) }
809
- }
810
- };
811
- await storeBundleInR2(storage, pluginId, newVersion, bundle);
812
- await stateRepo.upsert(pluginId, newVersion, "active", {
813
- source: "marketplace",
814
- marketplaceVersion: newVersion,
815
- displayName: pluginDetail.name,
816
- description: pluginDetail.description ?? void 0,
817
- mcpToolsEnabled: false,
818
- mcpToolsConsent: null
819
- });
820
- await syncDeclaredStorageIndexes(db, [bundle.manifest]);
821
- deleteBundleFromR2(storage, pluginId, oldVersion).catch(() => {});
822
- return {
823
- success: true,
824
- data: {
825
- pluginId,
826
- oldVersion,
827
- newVersion,
828
- capabilityChanges,
829
- routeVisibilityChanges: hasNewPublicRoutes ? routeVisibilityChanges : void 0
830
- }
831
- };
832
- } catch (err) {
833
- if (err instanceof MarketplaceUnavailableError) return {
834
- success: false,
835
- error: {
836
- code: "MARKETPLACE_UNAVAILABLE",
837
- message: "Marketplace is unavailable"
838
- }
839
- };
840
- if (err instanceof MarketplaceError) return {
841
- success: false,
842
- error: {
843
- code: err.code ?? "MARKETPLACE_ERROR",
844
- message: err.message
845
- }
846
- };
847
- console.error("Failed to update marketplace plugin:", err);
848
- return {
849
- success: false,
850
- error: {
851
- code: "UPDATE_FAILED",
852
- message: "Failed to update plugin"
853
- }
854
- };
855
- }
856
- }
857
- async function handleMarketplaceUninstall(db, storage, pluginId, opts) {
858
- try {
859
- const stateRepo = new PluginStateRepository(db);
860
- const existing = await stateRepo.get(pluginId);
861
- if (!existing || existing.source !== "marketplace") return {
862
- success: false,
863
- error: {
864
- code: "NOT_FOUND",
865
- message: `No marketplace plugin found: ${pluginId}`
866
- }
867
- };
868
- const version = existing.marketplaceVersion ?? existing.version;
869
- if (storage) await deleteBundleFromR2(storage, pluginId, version);
870
- let dataDeleted = false;
871
- if (opts?.deleteData) try {
872
- await db.deleteFrom("_plugin_storage").where("plugin_id", "=", pluginId).execute();
873
- dataDeleted = true;
874
- } catch {}
875
- try {
876
- await removeAllPluginIndexes(db, pluginId);
877
- } catch {}
878
- await stateRepo.delete(pluginId);
879
- return {
880
- success: true,
881
- data: {
882
- pluginId,
883
- dataDeleted
884
- }
885
- };
886
- } catch (err) {
887
- console.error("Failed to uninstall marketplace plugin:", err);
888
- return {
889
- success: false,
890
- error: {
891
- code: "UNINSTALL_FAILED",
892
- message: "Failed to uninstall plugin"
893
- }
894
- };
895
- }
896
- }
897
- async function handleMarketplaceUpdateCheck(db, marketplaceUrl) {
898
- const client = getClient(marketplaceUrl);
899
- if (!client) return {
900
- success: false,
901
- error: {
902
- code: "MARKETPLACE_NOT_CONFIGURED",
903
- message: "Marketplace is not configured"
904
- }
905
- };
906
- try {
907
- const marketplacePlugins = await new PluginStateRepository(db).getMarketplacePlugins();
908
- const items = [];
909
- for (const plugin of marketplacePlugins) try {
910
- const detail = await client.getPlugin(plugin.pluginId);
911
- const latest = detail.latestVersion?.version;
912
- const installed = plugin.marketplaceVersion ?? plugin.version;
913
- if (!latest) continue;
914
- const hasUpdate = latest !== installed;
915
- let capabilityChanges;
916
- let hasCapabilityChanges = false;
917
- if (hasUpdate && detail.latestVersion) {
918
- capabilityChanges = diffCapabilities(detail.capabilities ?? [], detail.latestVersion.capabilities ?? []);
919
- hasCapabilityChanges = capabilityChanges.added.length > 0 || capabilityChanges.removed.length > 0;
920
- }
921
- items.push({
922
- pluginId: plugin.pluginId,
923
- installed,
924
- latest: latest ?? installed,
925
- hasUpdate,
926
- hasCapabilityChanges,
927
- capabilityChanges: hasCapabilityChanges ? capabilityChanges : void 0,
928
- hasRouteVisibilityChanges: false
929
- });
930
- } catch (err) {
931
- console.warn(`Failed to check updates for ${plugin.pluginId}:`, err);
932
- }
933
- return {
934
- success: true,
935
- data: { items }
936
- };
937
- } catch (err) {
938
- if (err instanceof MarketplaceUnavailableError) return {
939
- success: false,
940
- error: {
941
- code: "MARKETPLACE_UNAVAILABLE",
942
- message: "Marketplace is unavailable"
943
- }
944
- };
945
- console.error("Failed to check marketplace updates:", err);
946
- return {
947
- success: false,
948
- error: {
949
- code: "UPDATE_CHECK_FAILED",
950
- message: "Failed to check for updates"
951
- }
952
- };
953
- }
954
- }
955
- async function handleMarketplaceSearch(marketplaceUrl, query, opts) {
956
- const client = getClient(marketplaceUrl);
957
- if (!client) return {
958
- success: false,
959
- error: {
960
- code: "MARKETPLACE_NOT_CONFIGURED",
961
- message: "Marketplace is not configured"
962
- }
963
- };
964
- try {
965
- return {
966
- success: true,
967
- data: await client.search(query, opts)
968
- };
969
- } catch (err) {
970
- if (err instanceof MarketplaceUnavailableError) return {
971
- success: false,
972
- error: {
973
- code: "MARKETPLACE_UNAVAILABLE",
974
- message: "Marketplace is unavailable"
975
- }
976
- };
977
- console.error("Failed to search marketplace:", err);
978
- return {
979
- success: false,
980
- error: {
981
- code: "SEARCH_FAILED",
982
- message: "Failed to search marketplace"
983
- }
984
- };
985
- }
986
- }
987
- async function handleMarketplaceGetPlugin(marketplaceUrl, pluginId) {
988
- const client = getClient(marketplaceUrl);
989
- if (!client) return {
990
- success: false,
991
- error: {
992
- code: "MARKETPLACE_NOT_CONFIGURED",
993
- message: "Marketplace is not configured"
994
- }
995
- };
996
- try {
997
- return {
998
- success: true,
999
- data: await client.getPlugin(pluginId)
1000
- };
1001
- } catch (err) {
1002
- if (err instanceof MarketplaceError && err.status === 404) return {
1003
- success: false,
1004
- error: {
1005
- code: "NOT_FOUND",
1006
- message: `Plugin not found: ${pluginId}`
1007
- }
1008
- };
1009
- if (err instanceof MarketplaceUnavailableError) return {
1010
- success: false,
1011
- error: {
1012
- code: "MARKETPLACE_UNAVAILABLE",
1013
- message: "Marketplace is unavailable"
1014
- }
1015
- };
1016
- console.error("Failed to get marketplace plugin:", err);
1017
- return {
1018
- success: false,
1019
- error: {
1020
- code: "GET_PLUGIN_FAILED",
1021
- message: "Failed to get plugin details"
1022
- }
1023
- };
1024
- }
1025
- }
1026
- async function handleThemeSearch(marketplaceUrl, query, opts) {
1027
- const client = getClient(marketplaceUrl);
1028
- if (!client) return {
1029
- success: false,
1030
- error: {
1031
- code: "MARKETPLACE_NOT_CONFIGURED",
1032
- message: "Marketplace is not configured"
1033
- }
1034
- };
1035
- try {
1036
- return {
1037
- success: true,
1038
- data: await client.searchThemes(query, opts)
1039
- };
1040
- } catch (err) {
1041
- if (err instanceof MarketplaceUnavailableError) return {
1042
- success: false,
1043
- error: {
1044
- code: "MARKETPLACE_UNAVAILABLE",
1045
- message: "Marketplace is unavailable"
1046
- }
1047
- };
1048
- console.error("Failed to search themes:", err);
1049
- return {
1050
- success: false,
1051
- error: {
1052
- code: "THEME_SEARCH_FAILED",
1053
- message: "Failed to search themes"
1054
- }
1055
- };
1056
- }
1057
- }
1058
- async function handleThemeGetDetail(marketplaceUrl, themeId) {
1059
- const client = getClient(marketplaceUrl);
1060
- if (!client) return {
1061
- success: false,
1062
- error: {
1063
- code: "MARKETPLACE_NOT_CONFIGURED",
1064
- message: "Marketplace is not configured"
1065
- }
1066
- };
1067
- try {
1068
- return {
1069
- success: true,
1070
- data: await client.getTheme(themeId)
1071
- };
1072
- } catch (err) {
1073
- if (err instanceof MarketplaceError && err.status === 404) return {
1074
- success: false,
1075
- error: {
1076
- code: "NOT_FOUND",
1077
- message: `Theme not found: ${themeId}`
1078
- }
1079
- };
1080
- if (err instanceof MarketplaceUnavailableError) return {
1081
- success: false,
1082
- error: {
1083
- code: "MARKETPLACE_UNAVAILABLE",
1084
- message: "Marketplace is unavailable"
1085
- }
1086
- };
1087
- console.error("Failed to get marketplace theme:", err);
1088
- return {
1089
- success: false,
1090
- error: {
1091
- code: "GET_THEME_FAILED",
1092
- message: "Failed to get theme details"
1093
- }
1094
- };
1095
- }
1096
- }
1097
-
1098
- //#endregion
1099
- //#region src/registry/artifact-fetch.ts
1100
- const TRAILING_DOT$1 = /\.+$/;
1101
- const HEADER_END = new Uint8Array([
1102
- 13,
1103
- 10,
1104
- 13,
1105
- 10
1106
- ]);
1107
- const CRLF = new Uint8Array([13, 10]);
1108
- const STATUS_LINE_RE = /^HTTP\/1\.[01] ([2-5][0-9]{2})(?: .*)?$/;
1109
- const HEADER_NAME_RE = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
1110
- const FOLDED_HEADER_RE = /^[ \t]/;
1111
- const CONTENT_LENGTH_RE = /^(0|[1-9][0-9]*)$/;
1112
- const CHUNK_SIZE_RE = /^[0-9A-Fa-f]+$/;
1113
- const MAX_HEADER_BYTES = 32 * 1024;
1114
- const LOCALHOST_HOSTNAMES = new Set([
1115
- "localhost",
1116
- "localhost.localdomain",
1117
- "ip6-localhost",
1118
- "ip6-loopback"
1119
- ]);
1120
- let defaultTransport = null;
1121
- function isLocalhostHostname(hostname) {
1122
- const normalized = hostname.toLowerCase().replace(TRAILING_DOT$1, "");
1123
- return LOCALHOST_HOSTNAMES.has(normalized) || normalized.endsWith(".localhost") || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]" || normalized.startsWith("::ffff:127.") || normalized.startsWith("::ffff:7f00:");
1124
- }
1125
- async function resolveSafeArtifactTarget(urlString) {
1126
- let url;
1127
- try {
1128
- url = new URL(urlString);
1129
- } catch {
1130
- throw new Error(`Invalid artifact URL: ${urlString}`);
1131
- }
1132
- if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error(`Artifact URL protocol not allowed: ${url.protocol}`);
1133
- if (url.username || url.password) throw new Error("Artifact URL must not contain embedded credentials");
1134
- const hostname = stripIpv6Brackets(url.hostname.toLowerCase().replace(TRAILING_DOT$1, ""));
1135
- const localhost = isLocalhostHostname(hostname);
1136
- if (!import.meta.env.DEV) {
1137
- if (url.protocol === "http:") throw new Error("Artifact URL must use https");
1138
- if (localhost) throw new Error(`Artifact URL points to localhost: ${hostname}`);
1139
- } else if (url.protocol === "http:" && !localhost) throw new Error("Artifact URL must use https (http allowed only for localhost in dev)");
1140
- if (localhost) return {
1141
- url,
1142
- addresses: []
1143
- };
1144
- try {
1145
- return await resolveAndValidateExternalUrlTarget(url.href);
1146
- } catch (error) {
1147
- if (error instanceof SsrfError) throw new Error(`Artifact URL rejected: ${error.message}`, { cause: error });
1148
- throw error;
1149
- }
1150
- }
1151
- async function fetchRegistryArtifactUrl(urlString, options) {
1152
- if (!Number.isSafeInteger(options.maxResponseBytes) || options.maxResponseBytes < 0) throw new TypeError("Registry artifact response limit is invalid");
1153
- const target = await resolveSafeArtifactTarget(urlString);
1154
- if (target.addresses.length === 0) return globalThis.fetch(target.url, {
1155
- redirect: "manual",
1156
- signal: options.signal
1157
- });
1158
- const result = await (defaultTransport ?? await createRuntimeRegistryArtifactTransport()).fetch({
1159
- url: target.url,
1160
- allowedAddresses: target.addresses,
1161
- signal: options.signal,
1162
- maxResponseBytes: options.maxResponseBytes
1163
- });
1164
- if (!target.addresses.includes(result.connectedAddress)) {
1165
- await result.response.body?.cancel().catch(() => void 0);
1166
- throw new Error("Registry artifact transport connected outside the validated address set");
1167
- }
1168
- return result.response;
1169
- }
1170
- async function createRuntimeRegistryArtifactTransport() {
1171
- try {
1172
- const connect = objectProperty(await import("cloudflare:sockets"), "connect");
1173
- if (!isWorkerSocketConnect(connect)) throw new TypeError("Workers socket binding is invalid");
1174
- return createWorkersRegistryArtifactTransport(connect);
1175
- } catch {
1176
- return createNodeRegistryArtifactTransport();
1177
- }
1178
- }
1179
- function createWorkersRegistryArtifactTransport(connect) {
1180
- return { async fetch(input) {
1181
- let lastError;
1182
- for (const address of input.allowedAddresses) {
1183
- if (input.signal.aborted) throw new Error("Registry artifact request was aborted");
1184
- let socket;
1185
- let tls;
1186
- try {
1187
- socket = connect({
1188
- hostname: address,
1189
- port: parseHttpsPort(input.url)
1190
- }, {
1191
- secureTransport: "starttls",
1192
- allowHalfOpen: false
1193
- });
1194
- await abortable(socket.opened, input.signal, () => socket?.close());
1195
- tls = socket.startTls({ expectedServerHostname: stripIpv6Brackets(input.url.hostname) });
1196
- await abortable(tls.opened, input.signal, () => tls?.close());
1197
- const writer = tls.writable.getWriter();
1198
- try {
1199
- await abortable(writer.write(buildHttpRequest(input.url)), input.signal, () => tls?.close());
1200
- } finally {
1201
- writer.releaseLock();
1202
- }
1203
- const bytes = await readWorkerSocketResponse(tls, input.maxResponseBytes + MAX_HEADER_BYTES, input.signal);
1204
- await tls.close().catch(() => void 0);
1205
- return {
1206
- response: parsedResponseToWebResponse(parsePinnedHttpResponse(bytes, input.maxResponseBytes)),
1207
- connectedAddress: address
1208
- };
1209
- } catch (error) {
1210
- await Promise.all([tls?.close().catch(() => void 0), socket?.close().catch(() => void 0)]);
1211
- if (input.signal.aborted) throw error;
1212
- lastError = error;
1213
- }
1214
- }
1215
- throw new Error("Registry artifact transport could not connect to an approved address", { cause: lastError });
1216
- } };
1217
- }
1218
- async function createNodeRegistryArtifactTransport() {
1219
- const { request } = await import("node:https");
1220
- return { async fetch(input) {
1221
- let lastError;
1222
- for (const address of input.allowedAddresses) try {
1223
- return {
1224
- response: await new Promise((resolve, reject) => {
1225
- const chunks = [];
1226
- let total = 0;
1227
- const req = request({
1228
- protocol: "https:",
1229
- hostname: stripIpv6Brackets(input.url.hostname),
1230
- port: parseHttpsPort(input.url),
1231
- path: `${input.url.pathname}${input.url.search}`,
1232
- method: "GET",
1233
- agent: false,
1234
- headers: {
1235
- Host: input.url.host,
1236
- Connection: "close",
1237
- "Accept-Encoding": "identity"
1238
- },
1239
- lookup: (_hostname, _options, callback) => {
1240
- callback(null, address, address.includes(":") ? 6 : 4);
1241
- },
1242
- signal: input.signal
1243
- }, (upstream) => {
1244
- const contentEncoding = upstream.headers["content-encoding"];
1245
- if (contentEncoding && contentEncoding.toLowerCase() !== "identity") {
1246
- upstream.destroy(/* @__PURE__ */ new Error("Registry artifact response content encoding is unsupported"));
1247
- return;
1248
- }
1249
- upstream.on("data", (chunk) => {
1250
- total += chunk.byteLength;
1251
- if (total > input.maxResponseBytes) {
1252
- upstream.destroy(/* @__PURE__ */ new RangeError("Registry artifact response exceeds its byte limit"));
1253
- return;
1254
- }
1255
- chunks.push(new Uint8Array(chunk));
1256
- });
1257
- upstream.once("error", reject);
1258
- upstream.once("end", () => {
1259
- const bytes = concatBytes(chunks, total);
1260
- const headers = new Headers();
1261
- for (let index = 0; index < upstream.rawHeaders.length; index += 2) headers.append(upstream.rawHeaders[index], upstream.rawHeaders[index + 1]);
1262
- resolve(parsedResponseToWebResponse({
1263
- status: upstream.statusCode ?? 502,
1264
- headers,
1265
- body: bytes
1266
- }));
1267
- });
1268
- });
1269
- req.once("error", reject);
1270
- req.end();
1271
- }),
1272
- connectedAddress: address
1273
- };
1274
- } catch (error) {
1275
- if (input.signal.aborted) throw error;
1276
- lastError = error;
1277
- }
1278
- throw new Error("Registry artifact transport could not connect to an approved address", { cause: lastError });
1279
- } };
1280
- }
1281
- function parsePinnedHttpResponse(bytes, maxResponseBytes) {
1282
- const headerEnd = findSequence(bytes, HEADER_END, 0);
1283
- if (headerEnd === -1 || headerEnd > MAX_HEADER_BYTES) throw new Error("Registry artifact response headers are invalid or too large");
1284
- const lines = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, headerEnd)).split("\r\n");
1285
- const statusMatch = STATUS_LINE_RE.exec(lines.shift() ?? "");
1286
- if (!statusMatch) throw new Error("Registry artifact response status line is invalid");
1287
- const headers = new Headers();
1288
- const rawHeaders = /* @__PURE__ */ new Map();
1289
- for (const line of lines) {
1290
- if (FOLDED_HEADER_RE.test(line)) throw new Error("Registry artifact response uses folded headers");
1291
- const separator = line.indexOf(":");
1292
- if (separator < 1) throw new Error("Registry artifact response header is invalid");
1293
- const name = line.slice(0, separator);
1294
- const value = line.slice(separator + 1).trim();
1295
- if (!HEADER_NAME_RE.test(name) || containsInvalidHeaderValue(value)) throw new Error("Registry artifact response header is invalid");
1296
- const normalized = name.toLowerCase();
1297
- const values = rawHeaders.get(normalized) ?? [];
1298
- values.push(value);
1299
- rawHeaders.set(normalized, values);
1300
- headers.append(name, value);
1301
- }
1302
- const contentEncoding = rawHeaders.get("content-encoding");
1303
- if (contentEncoding && (contentEncoding.length !== 1 || contentEncoding[0]?.toLowerCase() !== "identity")) throw new Error("Registry artifact response content encoding is unsupported");
1304
- const contentLength = rawHeaders.get("content-length");
1305
- const transferEncoding = rawHeaders.get("transfer-encoding");
1306
- if (contentLength && transferEncoding) throw new Error("Registry artifact response framing is ambiguous");
1307
- const bodyBytes = bytes.subarray(headerEnd + HEADER_END.length);
1308
- let body;
1309
- if (transferEncoding) {
1310
- if (transferEncoding.length !== 1 || transferEncoding[0]?.toLowerCase() !== "chunked") throw new Error("Registry artifact response transfer encoding is unsupported");
1311
- body = decodeChunkedBody(bodyBytes, maxResponseBytes);
1312
- } else if (contentLength) {
1313
- if (contentLength.length !== 1 || !CONTENT_LENGTH_RE.test(contentLength[0])) throw new Error("Registry artifact response content length is invalid");
1314
- const length = Number(contentLength[0]);
1315
- if (!Number.isSafeInteger(length) || length !== bodyBytes.byteLength) throw new Error("Registry artifact response body length does not match its framing");
1316
- body = new Uint8Array(bodyBytes);
1317
- } else body = new Uint8Array(bodyBytes);
1318
- if (body.byteLength > maxResponseBytes) throw new RangeError("Registry artifact response exceeds its byte limit");
1319
- return {
1320
- status: Number(statusMatch[1]),
1321
- headers,
1322
- body
1323
- };
1324
- }
1325
- function parsedResponseToWebResponse(parsed) {
1326
- const bodyAllowed = ![
1327
- 204,
1328
- 205,
1329
- 304
1330
- ].includes(parsed.status);
1331
- let body = null;
1332
- if (bodyAllowed) {
1333
- body = new ArrayBuffer(parsed.body.byteLength);
1334
- new Uint8Array(body).set(parsed.body);
1335
- }
1336
- return new Response(body, {
1337
- status: parsed.status,
1338
- headers: parsed.headers
1339
- });
1340
- }
1341
- function buildHttpRequest(url) {
1342
- return new TextEncoder().encode([
1343
- `GET ${url.pathname}${url.search} HTTP/1.1`,
1344
- `Host: ${url.host}`,
1345
- "Connection: close",
1346
- "Accept-Encoding: identity",
1347
- "",
1348
- ""
1349
- ].join("\r\n"));
1350
- }
1351
- async function readWorkerSocketResponse(socket, maximumBytes, signal) {
1352
- const reader = socket.readable.getReader();
1353
- const chunks = [];
1354
- let total = 0;
1355
- try {
1356
- for (;;) {
1357
- const next = await abortable(reader.read(), signal, () => socket.close());
1358
- if (next.done) break;
1359
- total += next.value.byteLength;
1360
- if (total > maximumBytes) throw new RangeError("Registry artifact response exceeds its byte limit");
1361
- chunks.push(next.value);
1362
- }
1363
- } finally {
1364
- reader.releaseLock();
1365
- }
1366
- return concatBytes(chunks, total);
1367
- }
1368
- function decodeChunkedBody(bytes, maximumBytes) {
1369
- const chunks = [];
1370
- let offset = 0;
1371
- let total = 0;
1372
- for (;;) {
1373
- const lineEnd = findSequence(bytes, CRLF, offset);
1374
- if (lineEnd === -1 || lineEnd - offset > 128) throw new Error("Registry artifact chunk size line is invalid");
1375
- const sizeText = new TextDecoder().decode(bytes.subarray(offset, lineEnd)).split(";", 1)[0];
1376
- if (!sizeText || !CHUNK_SIZE_RE.test(sizeText)) throw new Error("Registry artifact chunk size is invalid");
1377
- const size = Number.parseInt(sizeText, 16);
1378
- if (!Number.isSafeInteger(size)) throw new Error("Registry artifact chunk size is invalid");
1379
- offset = lineEnd + CRLF.length;
1380
- if (size === 0) {
1381
- if (offset + CRLF.length !== bytes.length || bytes[offset] !== 13 || bytes[offset + 1] !== 10) throw new Error("Registry artifact chunk trailer is invalid");
1382
- break;
1383
- }
1384
- if (offset + size + CRLF.length > bytes.length) throw new Error("Registry artifact chunk body is truncated");
1385
- if (bytes[offset + size] !== 13 || bytes[offset + size + 1] !== 10) throw new Error("Registry artifact chunk delimiter is invalid");
1386
- total += size;
1387
- if (total > maximumBytes) throw new RangeError("Registry artifact response exceeds its byte limit");
1388
- chunks.push(new Uint8Array(bytes.subarray(offset, offset + size)));
1389
- offset += size + CRLF.length;
1390
- }
1391
- return concatBytes(chunks, total);
1392
- }
1393
- function findSequence(bytes, sequence, start) {
1394
- outer: for (let offset = start; offset <= bytes.length - sequence.length; offset += 1) {
1395
- for (let index = 0; index < sequence.length; index += 1) if (bytes[offset + index] !== sequence[index]) continue outer;
1396
- return offset;
1397
- }
1398
- return -1;
1399
- }
1400
- function concatBytes(chunks, total) {
1401
- const bytes = new Uint8Array(total);
1402
- let offset = 0;
1403
- for (const chunk of chunks) {
1404
- bytes.set(chunk, offset);
1405
- offset += chunk.byteLength;
1406
- }
1407
- return bytes;
1408
- }
1409
- function containsInvalidHeaderValue(value) {
1410
- for (const character of value) {
1411
- const code = character.codePointAt(0);
1412
- if (code === 0 || code === 10 || code === 13) return true;
1413
- }
1414
- return false;
1415
- }
1416
- function parseHttpsPort(url) {
1417
- const port = url.port === "" ? 443 : Number(url.port);
1418
- if (!Number.isSafeInteger(port) || port < 1 || port > 65535) throw new TypeError("Registry artifact HTTPS port is invalid");
1419
- return port;
1420
- }
1421
- function stripIpv6Brackets(hostname) {
1422
- return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
1423
- }
1424
- function objectProperty(value, key) {
1425
- if (typeof value !== "object" || value === null) return void 0;
1426
- return Object.getOwnPropertyDescriptor(value, key)?.value;
1427
- }
1428
- function isWorkerSocketConnect(value) {
1429
- return typeof value === "function";
1430
- }
1431
- async function abortable(operation, signal, onAbort) {
1432
- if (signal.aborted) throw new Error("Registry artifact request was aborted");
1433
- let abort;
1434
- const aborted = new Promise((_resolve, reject) => {
1435
- abort = () => {
1436
- onAbort();
1437
- reject(/* @__PURE__ */ new Error("Registry artifact request was aborted"));
1438
- };
1439
- signal.addEventListener("abort", abort, { once: true });
1440
- });
1441
- try {
1442
- return await Promise.race([operation, aborted]);
1443
- } finally {
1444
- if (abort) signal.removeEventListener("abort", abort);
1445
- }
1446
- }
1447
-
1448
- //#endregion
1449
- //#region src/registry/config.ts
1450
- /**
1451
- * Helpers for normalizing the experimental registry integration option
1452
- * (`config.experimental.registry` in `astro.config.mjs`) into the shape
1453
- * exposed on the admin manifest.
1454
- *
1455
- * The integration option accepts a human-friendly duration string for
1456
- * `policy.minimumReleaseAge` (`"48h"`, `"7d"`); the manifest exposes
1457
- * seconds so the browser doesn't need a duration parser.
1458
- */
1459
- /**
1460
- * Canonicalize a capabilities list for set-style comparison.
1461
- *
1462
- * Capabilities (the legacy declared-access shape used by the current
1463
- * sandbox enforcer) are conceptually a *set*: order, duplicates, and
1464
- * non-string entries don't carry meaning. The install handler's drift
1465
- * check compares the admin's acknowledged set against the bundle
1466
- * manifest's set; both sides pass through this canonicalizer first so
1467
- * an aggregator-supplied array with unstable order or junk entries
1468
- * can't cause a spurious drift rejection.
1469
- *
1470
- * Filters non-strings, deduplicates, and sorts lexically. Named to
1471
- * avoid shadowing `@emdash-cms/plugin-types`'s existing
1472
- * `normalizeCapabilities` (which dedupes + applies the deprecated →
1473
- * current alias map but does not filter junk or sort).
1474
- *
1475
- * Exported so the same shape is produced by the browser before sending
1476
- * the `acknowledgedDeclaredAccess` payload and by the server before
1477
- * comparing against the bundle.
1478
- */
1479
- function canonicalCapabilitiesForDriftCheck(value) {
1480
- if (!Array.isArray(value)) return [];
1481
- const seen = /* @__PURE__ */ new Set();
1482
- for (const entry of value) if (typeof entry === "string" && entry.length > 0) seen.add(entry);
1483
- return [...seen].toSorted();
1484
- }
1485
- /**
1486
- * Returns whether a `(publisher_did, slug)` pair is on the
1487
- * minimum-release-age exemption list. Exported so the same matcher is
1488
- * used by the browser policy filter and the server-side install
1489
- * enforcement.
1490
- *
1491
- * Matching is DID-only. Handles are aggregator-supplied envelope data
1492
- * (mutable, controlled by an attacker who compromises the aggregator)
1493
- * and cannot be used as a trust input -- a compromised aggregator
1494
- * could claim any handle for any package and bypass the holdback. DIDs
1495
- * are part of the AT URI of the package record and are independently
1496
- * resolvable, so even a compromised aggregator can't lie about the
1497
- * publisher DID without also breaking checksum verification downstream.
1498
- *
1499
- * Entries from config are already lowercased at manifest-build time.
1500
- * Runtime values are lowercased here at compare time.
1501
- */
1502
- function releaseExemptFromMinimumAge(exclude, publisherDid, slug) {
1503
- if (!exclude || exclude.length === 0) return false;
1504
- const didLower = publisherDid.toLowerCase();
1505
- const fullDid = `${didLower}/${slug.toLowerCase()}`;
1506
- for (const entry of exclude) {
1507
- if (entry === didLower) return true;
1508
- if (entry === fullDid) return true;
1509
- }
1510
- return false;
1511
- }
1512
- const DURATION_PATTERN = /^(\d+)(s|m|h|d|w)$/;
1513
- /** Trailing slashes on the aggregator URL, stripped during normalization. */
1514
- const TRAILING_SLASHES = /\/+$/;
1515
- /** Trailing dot on a hostname, stripped before URL host comparisons. */
1516
- const TRAILING_DOT = /\.$/;
1517
- const REGISTRY_PACKAGE_SLUG_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
1518
- /**
1519
- * Parse a duration string or raw second count into a non-negative
1520
- * integer count of seconds. Throws on unrecognised input so config
1521
- * mistakes fail at startup rather than silently disabling the policy.
1522
- */
1523
- function parseDurationSeconds(duration) {
1524
- if (typeof duration === "number") {
1525
- if (!Number.isFinite(duration) || duration < 0) throw new Error(`Invalid duration: ${duration} (must be a non-negative finite number)`);
1526
- return Math.floor(duration);
1527
- }
1528
- const match = duration.match(DURATION_PATTERN);
1529
- if (!match) throw new Error(`Invalid duration format: "${duration}". Use a duration string like "48h", "7d", "30m", or a number of seconds.`);
1530
- const value = parseInt(match[1], 10);
1531
- const unit = match[2];
1532
- switch (unit) {
1533
- case "s": return value;
1534
- case "m": return value * 60;
1535
- case "h": return value * 60 * 60;
1536
- case "d": return value * 24 * 60 * 60;
1537
- case "w": return value * 7 * 24 * 60 * 60;
1538
- default: throw new Error(`Unknown duration unit: ${unit}`);
1539
- }
1540
- }
1541
- /**
1542
- * Validate that `aggregatorUrl` is a safe outbound target for the
1543
- * registry's XRPC calls. Same posture as artifact downloads: HTTPS
1544
- * required in production; `http://localhost` allowed only in dev.
1545
- *
1546
- * The aggregator's responses are the trust source for release records,
1547
- * checksums, labels, artifact cache descriptors, and `indexedAt` (until full MST
1548
- * verification lands). Allowing plain HTTP here would let a network
1549
- * attacker swap a release record and point the artifact URL at their
1550
- * own HTTPS bundle, defeating the checksum trust chain because the
1551
- * attacker controls the unsigned transport that supplied the checksum.
1552
- */
1553
- function validateAggregatorUrl(aggregatorUrl) {
1554
- let parsed;
1555
- try {
1556
- parsed = new URL(aggregatorUrl);
1557
- } catch {
1558
- throw new Error(`registry.aggregatorUrl is not a valid URL: ${aggregatorUrl}`);
1559
- }
1560
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`registry.aggregatorUrl must use http or https: ${aggregatorUrl}`);
1561
- if (parsed.username || parsed.password) throw new Error("registry.aggregatorUrl must not contain embedded credentials (user:pass@)");
1562
- const rawHostname = parsed.hostname.toLowerCase().replace(TRAILING_DOT, "");
1563
- const hostname = rawHostname.startsWith("[") && rawHostname.endsWith("]") ? rawHostname.slice(1, -1) : rawHostname;
1564
- const isLocalhost = hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname.startsWith("::ffff:127.") || hostname.startsWith("::ffff:7f00:");
1565
- if (!import.meta.env.DEV) {
1566
- if (parsed.protocol === "http:") throw new Error(`registry.aggregatorUrl must use https in production: ${aggregatorUrl}`);
1567
- if (isLocalhost) throw new Error(`registry.aggregatorUrl points at localhost; allowed only in dev: ${aggregatorUrl}`);
1568
- } else if (parsed.protocol === "http:" && !isLocalhost) throw new Error(`registry.aggregatorUrl must use https (http allowed only for localhost in dev): ${aggregatorUrl}`);
1569
- return parsed;
1570
- }
1571
- /**
1572
- * Expand the `RegistryConfigInput` shorthand into the full
1573
- * `RegistryConfig` object shape.
1574
- *
1575
- * Users can pass a bare aggregator URL string for the common case
1576
- * (`experimental.registry: "https://registry.emdashcms.com"`); the
1577
- * normalizer handles either form transparently.
1578
- *
1579
- * Returns `undefined` for `undefined` input so callers can chain with
1580
- * optional chaining.
1581
- */
1582
- function coerceRegistryConfig(input) {
1583
- if (input === void 0) return void 0;
1584
- if (typeof input === "string") return { aggregatorUrl: input };
1585
- return input;
1586
- }
1587
- /**
1588
- * Normalize the user-supplied `RegistryConfigInput` into the shape that
1589
- * ships to the admin browser via the manifest endpoint.
1590
- *
1591
- * Accepts either the shorthand string form
1592
- * (`"https://registry.emdashcms.com"`) or the full `RegistryConfig`
1593
- * object. Returns `null` when `input` is undefined so callers can
1594
- * spread the result directly into the manifest object.
1595
- *
1596
- * Throws if the aggregator URL is malformed, points at a forbidden host,
1597
- * or `policy.minimumReleaseAge` is unparseable. These surface at
1598
- * runtime startup as 500s from the manifest endpoint -- intended,
1599
- * because the alternative is silently disabling the registry on
1600
- * misconfigured sites.
1601
- *
1602
- * TODO: switch to a Zod schema for richer per-field error messages and
1603
- * to surface misconfigurations to the admin UI as a banner instead of
1604
- * a manifest 500.
1605
- */
1606
- function normalizeRegistryConfig(input) {
1607
- const config = coerceRegistryConfig(input);
1608
- if (!config) return null;
1609
- const aggregatorUrl = config.aggregatorUrl?.trim();
1610
- if (!aggregatorUrl) throw new Error("registry.aggregatorUrl is required when registry is configured");
1611
- validateAggregatorUrl(aggregatorUrl);
1612
- const out = { aggregatorUrl: aggregatorUrl.replace(TRAILING_SLASHES, "") };
1613
- if (config.acceptLabelers) out.acceptLabelers = config.acceptLabelers;
1614
- const policy = {};
1615
- let hasPolicy = false;
1616
- if (config.policy?.minimumReleaseAge !== void 0) {
1617
- policy.minimumReleaseAgeSeconds = parseDurationSeconds(config.policy.minimumReleaseAge);
1618
- hasPolicy = true;
1619
- }
1620
- if (config.policy?.minimumReleaseAgeExclude !== void 0) {
1621
- const list = config.policy.minimumReleaseAgeExclude.map((entry) => {
1622
- const trimmed = entry.trim();
1623
- if (!trimmed) throw new Error("registry.policy.minimumReleaseAgeExclude entries cannot be empty");
1624
- const lower = trimmed.toLowerCase();
1625
- const [did, slug, ...extra] = lower.split("/");
1626
- if (!did || !isDid(did) || extra.length > 0 || slug !== void 0 && !REGISTRY_PACKAGE_SLUG_PATTERN.test(slug)) throw new Error(`registry.policy.minimumReleaseAgeExclude entry must be a DID or <did>/<slug>: ${trimmed}`);
1627
- return lower;
1628
- });
1629
- if (list.length > 0) {
1630
- policy.minimumReleaseAgeExclude = list;
1631
- hasPolicy = true;
1632
- }
1633
- }
1634
- if (hasPolicy) out.policy = policy;
1635
- return out;
1636
- }
1637
-
1638
- //#endregion
1639
- //#region src/registry/plugin-id.ts
1640
- /**
1641
- * Plugin identifier helpers for the experimental decentralized plugin
1642
- * registry.
1643
- *
1644
- * Registry plugins are addressed by `(publisher_did, slug)`, but the
1645
- * EmDash runtime threads a single `pluginId: string` through every
1646
- * install primitive (R2 storage keys, `PluginStateRepository`,
1647
- * `syncMarketplacePlugins`, sandbox cache keys). Rather than refactor
1648
- * everything to carry a composite identifier, we normalize the registry
1649
- * tuple to an opaque content-addressed id that satisfies the existing
1650
- * `validatePluginIdentifier` shape (`/^[a-z][a-z0-9_-]*$/`).
1651
- *
1652
- * The normalized id is:
1653
- *
1654
- * `r_` + base32-encoded SHA-256(publisher_did + "\n" + slug), truncated.
1655
- *
1656
- * Properties:
1657
- *
1658
- * - Deterministic. The same `(publisher, slug)` always produces the
1659
- * same id, so re-resolving an installed plugin's metadata against
1660
- * the aggregator is a straightforward lookup keyed by the columns
1661
- * stored alongside `plugin_id` in `plugin_states`.
1662
- * - Collision-resistant. 80 bits of truncated hash; a 50% birthday
1663
- * collision happens around 2^40 distinct plugins, well beyond what
1664
- * this registry will ever index.
1665
- * - R2-safe. Lowercase alphanumerics + underscore (no hyphens), no
1666
- * `:` or `/`. Existing sandbox cache keys (`${pluginId}:${version}`)
1667
- * keep working because the id contains no `:`.
1668
- * - Syntactically distinct from typical marketplace plugin ids: the
1669
- * `r_` prefix plus exactly 16 base32 characters is unlikely to be
1670
- * chosen as a marketplace id. Not formally guaranteed by the
1671
- * validator -- marketplace ids may begin with `r_` and contain
1672
- * hyphens -- so the install handler also performs an explicit
1673
- * pre-existing-row check at the derived id and rejects any cross-
1674
- * source collision (`PLUGIN_ID_COLLISION`).
1675
- *
1676
- * Reverse lookup (id → publisher + slug) requires the `plugin_states`
1677
- * row -- the hash is one-way. That's intentional: any code path that
1678
- * needs the human-meaningful pair already has the state row in hand.
1679
- */
1680
- /** Length (in base32 characters) of the truncated hash portion of the id. */
1681
- const HASH_LENGTH = 16;
1682
- /** Total expected length of a registry plugin id. */
1683
- const REGISTRY_PLUGIN_ID_LENGTH = 2 + HASH_LENGTH;
1684
- const BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
1685
- /**
1686
- * RFC 4648 base32 encoding without padding, lowercase. Implemented inline
1687
- * rather than depending on a multibase library because (a) we only need
1688
- * lowercase base32 here, (b) we need it to run identically in workerd,
1689
- * Node, and the browser, and (c) the implementation is fewer lines than
1690
- * the import statement would be.
1691
- */
1692
- function base32Encode(bytes) {
1693
- let bits = 0;
1694
- let value = 0;
1695
- let out = "";
1696
- for (const byte of bytes) {
1697
- value = value << 8 | byte;
1698
- bits += 8;
1699
- while (bits >= 5) {
1700
- bits -= 5;
1701
- out += BASE32_ALPHABET[value >>> bits & 31];
1702
- }
1703
- }
1704
- if (bits > 0) out += BASE32_ALPHABET[value << 5 - bits & 31];
1705
- return out;
1706
- }
1707
- /**
1708
- * Derive the normalized plugin id for a registry-published plugin.
1709
- *
1710
- * Throws if either input is empty or whitespace-only -- a missing DID
1711
- * or slug is always a programming error in the install path, not a
1712
- * recoverable runtime condition.
1713
- */
1714
- async function makeRegistryPluginId(publisherDid, slug) {
1715
- const did = publisherDid.trim();
1716
- const s = slug.trim();
1717
- if (!did) throw new Error("makeRegistryPluginId: publisherDid is required");
1718
- if (!s) throw new Error("makeRegistryPluginId: slug is required");
1719
- const input = `${did}\n${s}`;
1720
- const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
1721
- return `r_${base32Encode(new Uint8Array(hashBuffer)).slice(0, HASH_LENGTH)}`;
1722
- }
1723
-
1724
- //#endregion
1725
- //#region src/api/handlers/registry.ts
1726
- /**
1727
- * Registry plugin install handler.
1728
- *
1729
- * Installs a plugin published to the experimental decentralized plugin
1730
- * registry described in RFC 0001. The install flow:
1731
- *
1732
- * 1. Resolve `(handle, slug)` to a publisher DID via the configured
1733
- * aggregator's `resolvePackage` XRPC.
1734
- * 2. Look up the requested release (or the policy-filtered latest one)
1735
- * via `getLatestRelease` / `listReleases`.
1736
- * 3. Require the aggregator's approved listing projection, then apply the
1737
- * independent release-age and environment policies.
1738
- * 4. Fetch the bundle artifact through an advertised record-scoped cache,
1739
- * the publisher PDS, or the publisher-declared URL.
1740
- * 5. Verify the artifact's multibase checksum against the signed
1741
- * release record's `artifacts.package.checksum`.
1742
- * 6. Extract `manifest.json` + `backend.js` + optional `admin.js` from
1743
- * the gzipped tar bundle.
1744
- * 7. Store the extracted files in site-local R2 under the
1745
- * `registry/<plugin-id>/<version>/` prefix.
1746
- * 8. Write a `plugin_states` row with `source = "registry"` and the
1747
- * `(publisher_did, slug)` pair so updates can be resolved later.
1748
- * 9. Sync the runtime so the plugin becomes active immediately.
1749
- *
1750
- * Known gaps (tracked separately):
1751
- *
1752
- * - The aggregator-supplied records are not yet cryptographically
1753
- * verified against the publisher's MST signature. The signed bytes
1754
- * and CIDs are passed through verbatim per the lexicon, but full
1755
- * PDS-direct verification with proof traversal is follow-up work.
1756
- * The artifact checksum is verified end-to-end against the value
1757
- * in the (aggregator-relayed) release record, which is the actual
1758
- * trust boundary for the bytes that end up in the sandbox.
1759
- * - Listing approval controls whether metadata is eligible for discovery.
1760
- * It is never treated as approval of plugin code: checksum, bundle,
1761
- * manifest, access, consent, sandbox, and environment gates remain
1762
- * independent below.
1763
- */
1764
- const RELEASE_EXTENSION_NSID = "com.emdashcms.experimental.package.releaseExtension";
1765
- /**
1766
- * Whether two `declaredAccess` blocks grant exactly the same enforced access --
1767
- * the same capabilities AND the same host allow-list. Both are lowered through
1768
- * the canonical converter so that constraint content (`allowedHosts`), not just
1769
- * the capability set, is part of the comparison. The capability-set consent
1770
- * gate is blind to host scope; this is what keeps a bundle from being installed
1771
- * with a wider (or simply different) host allow-list than its published record
1772
- * advertised and the user consented to.
1773
- */
1774
- function enforcedAccessEqual(a, b) {
1775
- const aa = declaredAccessToCapabilities(a);
1776
- const bb = declaredAccessToCapabilities(b);
1777
- return JSON.stringify(aa.capabilities.toSorted()) === JSON.stringify(bb.capabilities.toSorted()) && JSON.stringify(aa.allowedHosts.toSorted()) === JSON.stringify(bb.allowedHosts.toSorted());
1778
- }
1779
- /** Matches a bare 64-character lowercase/uppercase hex SHA-256 digest. */
1780
- const SHA256_HEX_PATTERN = /^[a-f0-9]{64}$/i;
1781
- /** Compute the SHA-256 of `bytes` as a lowercase hex string. */
1782
- async function sha256Hex(bytes) {
1783
- const buf = await crypto.subtle.digest("SHA-256", bytes);
1784
- const arr = new Uint8Array(buf);
1785
- return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
1786
- }
1787
- /** multihash code for sha2-256 (single-byte varint). */
1788
- const MULTIHASH_SHA256_CODE = 18;
1789
- /** sha2-256 digest length in bytes (single-byte varint). */
1790
- const MULTIHASH_SHA256_LENGTH = 32;
1791
- /**
1792
- * Compute the multibase-multihash sha2-256 checksum of `bytes`, in the
1793
- * same `b<base32>` shape the registry CLI publishes
1794
- * (`packages/plugin-cli/src/multihash.ts`). Returns a 56-character
1795
- * string starting with `b`.
1796
- *
1797
- * The trust contract is: if both sides produce the same string for
1798
- * the same bytes, the bytes are unchanged. We don't decode the
1799
- * publisher-supplied checksum -- we just re-encode our own and compare,
1800
- * which is equivalent and avoids needing a base32 decoder.
1801
- */
1802
- async function sha256MultibaseMultihash(bytes) {
1803
- const digestBuf = await crypto.subtle.digest("SHA-256", bytes);
1804
- const digest = new Uint8Array(digestBuf);
1805
- const multihash = new Uint8Array(2 + digest.length);
1806
- multihash[0] = MULTIHASH_SHA256_CODE;
1807
- multihash[1] = MULTIHASH_SHA256_LENGTH;
1808
- multihash.set(digest, 2);
1809
- const { toBase32 } = await import("@atcute/multibase");
1810
- return `b${toBase32(multihash)}`;
1811
- }
1812
- /**
1813
- * Verify that a checksum string from a release record's
1814
- * `artifact.checksum` field corresponds to the SHA-256 of the given
1815
- * bytes.
1816
- *
1817
- * Accepts two formats:
1818
- *
1819
- * - Bare lowercase/uppercase hex SHA-256 (64 chars). Convenience for
1820
- * publishers / tools that emit hex rather than multibase.
1821
- * - Multibase-multihash with the `b` (base32) prefix and sha2-256.
1822
- * This is the format RFC 0001 mandates and the registry CLI emits
1823
- * (see `packages/plugin-cli/src/multihash.ts`).
1824
- *
1825
- * Hash functions other than sha2-256 are out of scope for this
1826
- * initial release; the install fails closed.
1827
- */
1828
- async function verifyChecksum(bytes, checksum) {
1829
- if (SHA256_HEX_PATTERN.test(checksum)) {
1830
- const actual = await sha256Hex(bytes);
1831
- return checksum.toLowerCase() === actual;
1832
- }
1833
- if (checksum.length === 56 && checksum.startsWith("b")) return (await sha256MultibaseMultihash(bytes)).toLowerCase() === checksum.toLowerCase();
1834
- return false;
1835
- }
1836
- /**
1837
- * Bytes-per-artifact cap on the gzipped tarball we'll download before
1838
- * decompression. RFC 0001 caps a sandboxed plugin bundle at 256 KiB
1839
- * decompressed (see `MAX_BUNDLE_SIZE` in cli/commands/bundle-utils.ts);
1840
- * gzip on a mix of JSON manifest + JS code typically gives 0.3-0.6
1841
- * ratio, so compressed bundles are well under 200 KiB in practice.
1842
- * 512 KiB leaves margin for unusual file mixes that compress poorly
1843
- * while still rejecting anything that's obviously not a legitimate
1844
- * plugin bundle.
1845
- */
1846
- const MAX_ARTIFACT_BYTES = 512 * 1024;
1847
- /**
1848
- * Maximum number of HTTP redirects followed during artifact download.
1849
- * Each hop is independently URL-validated, so a malicious server cannot
1850
- * redirect through a series of allowed-looking origins to reach a
1851
- * forbidden one.
1852
- */
1853
- const MAX_REDIRECTS = 5;
1854
- /**
1855
- * Wall-clock cap on any single artifact fetch attempt (per URL).
1856
- * Defends against slow-loris artifact sources that accept the connection but
1857
- * never finish sending headers or body.
1858
- */
1859
- const ARTIFACT_FETCH_TIMEOUT_MS = 15e3;
1860
- /**
1861
- * Total wall-clock budget for the artifact-download phase across all
1862
- * advertised caches, the publisher PDS, and the declared URL. Even with the
1863
- * per-URL timeout, unavailable sources could otherwise tie up the install request for
1864
- * minutes; this caps total time at a budget interactive admins can
1865
- * tolerate. Tuned so a fast happy path takes <1s of budget per
1866
- * attempt and a worst case still completes in under a minute.
1867
- */
1868
- const ARTIFACT_TOTAL_BUDGET_MS = 45e3;
1869
- /**
1870
- * Per-request timeout applied to every aggregator XRPC call
1871
- * (`resolvePackage`, `getLatestRelease`, `listReleases`). Matches the
1872
- * per-URL artifact-fetch cap. Without this, a slow-loris aggregator
1873
- * can stall the install before the artifact phase even starts.
1874
- */
1875
- const AGGREGATOR_REQUEST_TIMEOUT_MS = 15e3;
1876
- /**
1877
- * Total wall-clock budget for the aggregator-discovery phase
1878
- * (resolve + selected-release lookup). Mirrors the artifact-download
1879
- * budget. Worst case with the pinned-version path's 20-page cap is
1880
- * 20 + 1 calls; capping the total ensures any one stalled call
1881
- * still bounds the whole phase.
1882
- */
1883
- const AGGREGATOR_TOTAL_BUDGET_MS = 3e4;
1884
- /** Build a fetch function that enforces a per-request and per-budget timeout. */
1885
- function timedFetch(totalDeadline) {
1886
- return (input, init) => {
1887
- const now = Date.now();
1888
- const remaining = Math.max(0, totalDeadline - now);
1889
- if (remaining === 0) return Promise.reject(/* @__PURE__ */ new Error("Aggregator request budget exhausted"));
1890
- const timeout = Math.min(AGGREGATOR_REQUEST_TIMEOUT_MS, remaining);
1891
- const controller = new AbortController();
1892
- const timer = setTimeout(() => controller.abort(), timeout);
1893
- const callerSignal = init?.signal;
1894
- if (callerSignal) if (callerSignal.aborted) controller.abort(callerSignal.reason);
1895
- else callerSignal.addEventListener("abort", () => controller.abort(callerSignal.reason));
1896
- return fetch(input, {
1897
- ...init,
1898
- signal: controller.signal
1899
- }).finally(() => {
1900
- clearTimeout(timer);
1901
- });
1902
- };
1903
- }
1904
- async function fetchArtifact(artifactCaches, artifact, record, auth) {
1905
- const result = await fetchReleaseArtifact({
1906
- artifact,
1907
- record,
1908
- artifactCaches,
1909
- ...auth === void 0 ? {} : { auth }
1910
- }, {
1911
- fetch: async (url, init) => {
1912
- if (!init.signal) throw new Error("Registry artifact fetch requires an abort signal");
1913
- return fetchRegistryArtifactUrl(url.href, {
1914
- signal: init.signal,
1915
- maxResponseBytes: MAX_ARTIFACT_BYTES
1916
- });
1917
- },
1918
- resolveHostname: async (hostname) => (await resolveAndValidateExternalUrlTarget(`https://${hostname}`)).addresses,
1919
- allowHttpLocalhost: import.meta.env.DEV,
1920
- headerTimeoutMs: ARTIFACT_FETCH_TIMEOUT_MS,
1921
- totalTimeoutMs: ARTIFACT_TOTAL_BUDGET_MS,
1922
- maxBytes: MAX_ARTIFACT_BYTES,
1923
- maxRedirects: MAX_REDIRECTS
1924
- });
1925
- if (!result.success) throw new Error(`Artifact retrieval failed (${result.error.code}): ${result.error.message}`);
1926
- return result.value.bytes;
1927
- }
1928
- /**
1929
- * Gate a release's `requires` constraints against the running host
1930
- * environment. `requires` is the lexicon-`unknown` value off the signed
1931
- * release record — never trust its shape; `checkEnvCompatibility` guards it.
1932
- *
1933
- * Returns `null` when every advertised constraint is satisfied (or there are
1934
- * none), or a structured `ENV_INCOMPATIBLE` error naming the unsatisfied
1935
- * constraints and the host versions. The error carries the guarded `requires`
1936
- * and `host` maps so the admin can render the same mismatch the UI gate shows.
1937
- */
1938
- function assertEnvCompatible(requires, hostEnv) {
1939
- for (const skipped of findSkippedEnvConstraints(requires, hostEnv)) console.warn(`[registry] env compatibility constraint skipped: ${skipped.key} requires ${skipped.required} but host version is ${skipped.reason}`);
1940
- const mismatches = checkEnvCompatibility(requires, hostEnv);
1941
- if (mismatches.length === 0) return null;
1942
- const guarded = {};
1943
- for (const m of mismatches) guarded[m.key] = m.required;
1944
- return {
1945
- code: "ENV_INCOMPATIBLE",
1946
- message: `This release is not compatible with the current environment: ${mismatches.map((m) => `${m.key} requires ${m.required} but host is ${m.host}`).join("; ")}.`,
1947
- details: {
1948
- requires: guarded,
1949
- host: hostEnv
1950
- }
1951
- };
1952
- }
1953
- async function handleRegistryInstall(db, storage, sandboxRunner, registryConfigInput, input, opts) {
1954
- const registryConfig = coerceRegistryConfig(registryConfigInput);
1955
- if (!registryConfig) return {
1956
- success: false,
1957
- error: {
1958
- code: "REGISTRY_NOT_CONFIGURED",
1959
- message: "Registry is not configured"
1960
- }
1961
- };
1962
- if (!storage) return {
1963
- success: false,
1964
- error: {
1965
- code: "STORAGE_NOT_CONFIGURED",
1966
- message: "Storage is required for registry plugin installation"
1967
- }
1968
- };
1969
- if (!sandboxRunner || !sandboxRunner.isAvailable()) return {
1970
- success: false,
1971
- error: {
1972
- code: "SANDBOX_NOT_AVAILABLE",
1973
- message: "Sandbox runner is required for registry plugins"
1974
- }
1975
- };
1976
- try {
1977
- validateAggregatorUrl(registryConfig.aggregatorUrl);
1978
- } catch (err) {
1979
- return {
1980
- success: false,
1981
- error: {
1982
- code: "REGISTRY_NOT_CONFIGURED",
1983
- message: err instanceof Error ? err.message : "Invalid aggregator URL"
1984
- }
1985
- };
1986
- }
1987
- const { did, slug, version: requestedVersion } = input;
1988
- const { DiscoveryClient, registryLabelerPolicy } = await import("@emdash-cms/registry-client/discovery");
1989
- const aggregatorDeadline = Date.now() + AGGREGATOR_TOTAL_BUDGET_MS;
1990
- const discovery = new DiscoveryClient({
1991
- aggregatorUrl: registryConfig.aggregatorUrl,
1992
- acceptLabelers: registryConfig.acceptLabelers,
1993
- labelerPolicy: registryLabelerPolicy(registryConfig.acceptLabelers),
1994
- fetch: timedFetch(aggregatorDeadline)
1995
- });
1996
- if (!did.startsWith("did:") || did.split(":").length < 3) return {
1997
- success: false,
1998
- error: {
1999
- code: "INVALID_DID",
2000
- message: "DID must be a valid atproto DID (e.g. did:plc:abc123)"
2001
- }
2002
- };
2003
- try {
2004
- const publisherDid = did;
2005
- const packageView = await discovery.getPackage({
2006
- did: publisherDid,
2007
- slug
2008
- });
2009
- const MAX_LIST_PAGES = 20;
2010
- const releaseView = await (async () => {
2011
- if (!requestedVersion) return discovery.getLatestRelease({
2012
- did: publisherDid,
2013
- package: slug
2014
- });
2015
- let cursor;
2016
- const seenCursors = /* @__PURE__ */ new Set();
2017
- for (let page = 0; page < MAX_LIST_PAGES; page++) {
2018
- if (cursor !== void 0) {
2019
- if (seenCursors.has(cursor)) break;
2020
- seenCursors.add(cursor);
2021
- }
2022
- const result = await discovery.listReleases({
2023
- did: publisherDid,
2024
- package: slug,
2025
- cursor,
2026
- limit: 50
2027
- });
2028
- for (const r of result.releases) if (r.version === requestedVersion) return r;
2029
- if (!result.cursor) break;
2030
- cursor = result.cursor;
2031
- }
2032
- })();
2033
- if (!releaseView) return {
2034
- success: false,
2035
- error: {
2036
- code: "NO_RELEASE",
2037
- message: requestedVersion ? `Version ${requestedVersion} not found for ${publisherDid}/${slug}` : `No installable release found for ${publisherDid}/${slug}`
2038
- }
2039
- };
2040
- const signedRelease = releaseView.release;
2041
- if (packageView.did !== publisherDid || packageView.slug !== slug) return {
2042
- success: false,
2043
- error: {
2044
- code: "AGGREGATOR_IDENTITY_MISMATCH",
2045
- message: "Aggregator returned a package view for a different publisher or slug."
2046
- }
2047
- };
2048
- if (releaseView.did !== publisherDid || releaseView.package !== slug || signedRelease?.package !== slug || requestedVersion !== void 0 && releaseView.version !== requestedVersion || signedRelease?.version !== releaseView.version) return {
2049
- success: false,
2050
- error: {
2051
- code: "AGGREGATOR_IDENTITY_MISMATCH",
2052
- message: "Aggregator returned a release view that does not match the requested package or version."
2053
- }
2054
- };
2055
- const version = releaseView.version;
2056
- if (evaluateRegistryReleaseWithdrawal(releaseView, discovery.labelerPolicy).withdrawn) return {
2057
- success: false,
2058
- error: {
2059
- code: "RELEASE_YANKED",
2060
- message: "This release has been withdrawn"
2061
- }
2062
- };
2063
- if (opts?.hostEnv) {
2064
- const envError = assertEnvCompatible(releaseView.release?.requires, opts.hostEnv);
2065
- if (envError) return {
2066
- success: false,
2067
- error: envError
2068
- };
2069
- }
2070
- const minimumReleaseAge = registryConfig.policy?.minimumReleaseAge;
2071
- let minimumReleaseAgeSeconds = 0;
2072
- if (minimumReleaseAge !== void 0) try {
2073
- minimumReleaseAgeSeconds = parseDurationSeconds(minimumReleaseAge);
2074
- } catch (err) {
2075
- return {
2076
- success: false,
2077
- error: {
2078
- code: "REGISTRY_POLICY_INVALID",
2079
- message: err instanceof Error ? err.message : "Invalid minimumReleaseAge value in registry config"
2080
- }
2081
- };
2082
- }
2083
- if (minimumReleaseAgeSeconds > 0) {
2084
- const exclude = registryConfig.policy?.minimumReleaseAgeExclude?.map((e) => e.trim().toLowerCase());
2085
- if (!releaseExemptFromMinimumAge(exclude, publisherDid, slug)) {
2086
- const indexedAt = Date.parse(releaseView.indexedAt);
2087
- if (!Number.isFinite(indexedAt)) return {
2088
- success: false,
2089
- error: {
2090
- code: "RELEASE_TIMESTAMP_INVALID",
2091
- message: "Release record is missing a valid indexed-at timestamp; cannot evaluate minimum release age policy."
2092
- }
2093
- };
2094
- const ageSeconds = (Date.now() - indexedAt) / 1e3;
2095
- if (ageSeconds < minimumReleaseAgeSeconds) {
2096
- const remaining = Math.ceil(minimumReleaseAgeSeconds - ageSeconds);
2097
- return {
2098
- success: false,
2099
- error: {
2100
- code: "RELEASE_TOO_NEW",
2101
- message: `This release does not meet the configured minimum release age of ${minimumReleaseAgeSeconds}s. It will be installable in ~${remaining}s.`
2102
- }
2103
- };
2104
- }
2105
- }
2106
- }
2107
- const pluginId = await makeRegistryPluginId(publisherDid, slug);
2108
- if (opts?.configuredPluginIds?.has(pluginId)) return {
2109
- success: false,
2110
- error: {
2111
- code: "PLUGIN_ID_CONFLICT",
2112
- message: "A configured plugin with the same derived id already exists"
2113
- }
2114
- };
2115
- const stateRepo = new PluginStateRepository(db);
2116
- const existing = await stateRepo.get(pluginId);
2117
- if (existing) {
2118
- if (existing.source === "registry") return {
2119
- success: false,
2120
- error: {
2121
- code: "ALREADY_INSTALLED",
2122
- message: `Plugin ${publisherDid}/${slug} is already installed`
2123
- }
2124
- };
2125
- return {
2126
- success: false,
2127
- error: {
2128
- code: "PLUGIN_ID_COLLISION",
2129
- message: `A non-registry plugin already exists at the derived id ${pluginId}. Uninstall it before installing this registry plugin.`
2130
- }
2131
- };
2132
- }
2133
- const release = releaseView.release;
2134
- const packageArtifact = release?.artifacts?.package;
2135
- const declaredChecksum = packageArtifact?.checksum;
2136
- if (!packageArtifact || !declaredChecksum) return {
2137
- success: false,
2138
- error: {
2139
- code: "INVALID_RELEASE",
2140
- message: "Release record is missing its package artifact or checksum"
2141
- }
2142
- };
2143
- const artifactBytes = await fetchArtifact(releaseView.artifactCaches ?? [], packageArtifact, {
2144
- did: publisherDid,
2145
- collection: NSID.packageRelease,
2146
- rkey: `${slug}:${version}`,
2147
- cid: releaseView.cid
2148
- }, release.auth);
2149
- if (!await verifyChecksum(artifactBytes, declaredChecksum)) return {
2150
- success: false,
2151
- error: {
2152
- code: "CHECKSUM_MISMATCH",
2153
- message: "Artifact bytes do not match the release record's checksum, or the checksum encoding is unsupported."
2154
- }
2155
- };
2156
- let bundle;
2157
- try {
2158
- bundle = await extractBundle(artifactBytes);
2159
- } catch (err) {
2160
- return {
2161
- success: false,
2162
- error: {
2163
- code: "INVALID_BUNDLE",
2164
- message: err instanceof Error ? err.message : "Failed to extract plugin bundle"
2165
- }
2166
- };
2167
- }
2168
- if (bundle.manifest.version !== version) return {
2169
- success: false,
2170
- error: {
2171
- code: "MANIFEST_VERSION_MISMATCH",
2172
- message: `Bundle manifest version (${bundle.manifest.version}) does not match release version (${version})`
2173
- }
2174
- };
2175
- if (bundle.manifest.id !== slug) return {
2176
- success: false,
2177
- error: {
2178
- code: "MANIFEST_ID_MISMATCH",
2179
- message: `Bundle manifest id (${bundle.manifest.id}) does not match registry slug (${slug})`
2180
- }
2181
- };
2182
- bundle.manifest = {
2183
- ...bundle.manifest,
2184
- id: pluginId
2185
- };
2186
- const recordExt = (release?.extensions)?.[RELEASE_EXTENSION_NSID];
2187
- if (!enforcedAccessEqual(recordExt?.declaredAccess ?? {}, bundle.manifest.declaredAccess ?? {})) return {
2188
- success: false,
2189
- error: {
2190
- code: "DECLARED_ACCESS_DRIFT",
2191
- message: "The plugin bundle declares different permissions than its published record. Installation refused."
2192
- }
2193
- };
2194
- const actualCapabilities = canonicalCapabilitiesForDriftCheck(bundle.manifest.capabilities);
2195
- if (actualCapabilities.length > 0) {
2196
- if (input.acknowledgedDeclaredAccess === void 0) return {
2197
- success: false,
2198
- error: {
2199
- code: "DECLARED_ACCESS_REQUIRED",
2200
- message: "This plugin declares capabilities that require consent. Re-open the install dialog to review and acknowledge them."
2201
- }
2202
- };
2203
- const acknowledged = canonicalCapabilitiesForDriftCheck(input.acknowledgedDeclaredAccess);
2204
- if (acknowledged.length !== actualCapabilities.length || acknowledged.some((cap, i) => cap !== actualCapabilities[i])) return {
2205
- success: false,
2206
- error: {
2207
- code: "DECLARED_ACCESS_DRIFT",
2208
- message: "Plugin manifest has changed since you consented. Re-open the install dialog to review the new permissions."
2209
- }
2210
- };
2211
- }
2212
- const actualMcpTools = (bundle.manifest.mcp?.tools ?? []).map(({ inputSchema: _, outputSchema: __, ...tool }) => tool);
2213
- if (actualMcpTools.length > 0) {
2214
- if (JSON.stringify(input.acknowledgedMcpTools) !== JSON.stringify(actualMcpTools)) return {
2215
- success: false,
2216
- error: {
2217
- code: "MCP_TOOL_CONSENT_REQUIRED",
2218
- message: "Plugin MCP tools require explicit consent",
2219
- details: { mcpTools: actualMcpTools }
2220
- }
2221
- };
2222
- }
2223
- await storeBundleInR2(storage, pluginId, version, bundle, "registry");
2224
- const profile = packageView.profile;
2225
- try {
2226
- await stateRepo.upsert(pluginId, version, "active", {
2227
- source: "registry",
2228
- displayName: profile?.name ?? slug,
2229
- description: profile?.description ?? void 0,
2230
- registryPublisherDid: publisherDid,
2231
- registrySlug: slug
2232
- });
2233
- } catch (stateErr) {
2234
- let lostRace = false;
2235
- try {
2236
- const winner = await stateRepo.get(pluginId);
2237
- lostRace = winner !== void 0 && winner !== null;
2238
- } catch (probeErr) {
2239
- console.warn(`[registry-install] Failed to probe state row for ${pluginId} after state-write failure; treating as orphan:`, probeErr);
2240
- }
2241
- if (!lostRace) try {
2242
- await deleteBundleFromR2(storage, pluginId, version, "registry");
2243
- } catch (cleanupErr) {
2244
- console.warn(`[registry-install] Failed to clean up R2 bundle for ${pluginId}@${version} after state-row write failure:`, cleanupErr);
2245
- }
2246
- throw stateErr;
2247
- }
2248
- await syncDeclaredStorageIndexes(db, [bundle.manifest]);
2249
- return {
2250
- success: true,
2251
- data: {
2252
- pluginId,
2253
- publisherDid,
2254
- slug,
2255
- version,
2256
- capabilities: bundle.manifest.capabilities
2257
- }
2258
- };
2259
- } catch (err) {
2260
- if (err instanceof ClientValidationError) return {
2261
- success: false,
2262
- error: {
2263
- code: "AGGREGATOR_RESPONSE_INVALID",
2264
- message: `Aggregator returned a response that does not conform to its lexicon (${err.target})`
2265
- }
2266
- };
2267
- if (err instanceof ClientResponseError) {
2268
- if (err.error === "ListingUnavailable") return {
2269
- success: false,
2270
- error: {
2271
- code: "LISTING_UNAVAILABLE",
2272
- message: "This plugin is unavailable under the active registry policy"
2273
- }
2274
- };
2275
- return {
2276
- success: false,
2277
- error: {
2278
- code: err.status === 404 ? "AGGREGATOR_NOT_FOUND" : "AGGREGATOR_HTTP_ERROR",
2279
- message: `Aggregator returned ${err.status}: ${err.error}`
2280
- }
2281
- };
2282
- }
2283
- if (err instanceof EmDashStorageError) return {
2284
- success: false,
2285
- error: {
2286
- code: err.code ?? "STORAGE_ERROR",
2287
- message: "Storage error while installing plugin"
2288
- }
2289
- };
2290
- console.error("[registry-install] Failed:", err);
2291
- return {
2292
- success: false,
2293
- error: {
2294
- code: "INSTALL_FAILED",
2295
- message: err instanceof Error ? err.message : "Failed to install plugin from registry"
2296
- }
2297
- };
2298
- }
2299
- }
2300
- /**
2301
- * Uninstall a registry-source plugin. Deletes the R2 bundle under
2302
- * `registry/<pluginId>/<version>/`, optionally drops the plugin's
2303
- * `_plugin_storage` rows, and removes the `_plugin_state` row. The
2304
- * sandbox runtime is reconciled by the route's `syncRegistryPlugins`
2305
- * call after this returns.
2306
- *
2307
- * Refuses to uninstall plugins whose `source` is not `"registry"` to
2308
- * avoid trashing a marketplace/config plugin that happens to share the
2309
- * pluginId namespace.
2310
- */
2311
- async function handleRegistryUninstall(db, storage, pluginId, opts) {
2312
- try {
2313
- const stateRepo = new PluginStateRepository(db);
2314
- const existing = await stateRepo.get(pluginId);
2315
- if (!existing || existing.source !== "registry") return {
2316
- success: false,
2317
- error: {
2318
- code: "NOT_FOUND",
2319
- message: `No registry plugin found: ${pluginId}`
2320
- }
2321
- };
2322
- const version = existing.version;
2323
- let dataDeleted = false;
2324
- if (opts?.deleteData) {
2325
- await db.deleteFrom("_plugin_storage").where("plugin_id", "=", pluginId).execute();
2326
- dataDeleted = true;
2327
- }
2328
- if (storage) await deleteBundleFromR2(storage, pluginId, version, "registry");
2329
- try {
2330
- await removeAllPluginIndexes(db, pluginId);
2331
- } catch {}
2332
- await stateRepo.delete(pluginId);
2333
- return {
2334
- success: true,
2335
- data: {
2336
- pluginId,
2337
- dataDeleted
2338
- }
2339
- };
2340
- } catch (err) {
2341
- console.error("[registry-uninstall] Failed:", err);
2342
- return {
2343
- success: false,
2344
- error: {
2345
- code: "UNINSTALL_FAILED",
2346
- message: "Failed to uninstall plugin"
2347
- }
2348
- };
2349
- }
2350
- }
2351
- /**
2352
- * Update a registry-source plugin to a newer release. Mirrors
2353
- * `handleMarketplaceUpdate`: resolves the target version via the aggregator,
2354
- * re-runs the artifact fetch / checksum / extract pipeline, diffs capabilities
2355
- * and route visibility against the currently installed bundle, and gates
2356
- * escalations behind `confirmCapabilityChanges` / `confirmRouteVisibilityChanges`
2357
- * so the admin re-consents to widened permissions.
2358
- *
2359
- * Refuses non-registry sources. Refuses when the stored state row is missing
2360
- * the `(publisherDid, slug)` it needs to resolve against the aggregator.
2361
- */
2362
- async function handleRegistryUpdate(db, storage, sandboxRunner, registryConfigInput, pluginId, opts) {
2363
- const registryConfig = coerceRegistryConfig(registryConfigInput);
2364
- if (!registryConfig) return {
2365
- success: false,
2366
- error: {
2367
- code: "REGISTRY_NOT_CONFIGURED",
2368
- message: "Registry is not configured"
2369
- }
2370
- };
2371
- if (!storage) return {
2372
- success: false,
2373
- error: {
2374
- code: "STORAGE_NOT_CONFIGURED",
2375
- message: "Storage is required for registry plugin updates"
2376
- }
2377
- };
2378
- if (!sandboxRunner || !sandboxRunner.isAvailable()) return {
2379
- success: false,
2380
- error: {
2381
- code: "SANDBOX_NOT_AVAILABLE",
2382
- message: "Sandbox runner is required"
2383
- }
2384
- };
2385
- try {
2386
- validateAggregatorUrl(registryConfig.aggregatorUrl);
2387
- } catch (err) {
2388
- return {
2389
- success: false,
2390
- error: {
2391
- code: "REGISTRY_NOT_CONFIGURED",
2392
- message: err instanceof Error ? err.message : "Invalid aggregator URL"
2393
- }
2394
- };
2395
- }
2396
- try {
2397
- const stateRepo = new PluginStateRepository(db);
2398
- const existing = await stateRepo.get(pluginId);
2399
- if (!existing || existing.source !== "registry") return {
2400
- success: false,
2401
- error: {
2402
- code: "NOT_FOUND",
2403
- message: `No registry plugin found: ${pluginId}`
2404
- }
2405
- };
2406
- if (!existing.registryPublisherDid || !existing.registrySlug) return {
2407
- success: false,
2408
- error: {
2409
- code: "INVALID_STATE",
2410
- message: `Registry plugin ${pluginId} is missing publisher DID or slug in state`
2411
- }
2412
- };
2413
- const oldVersion = existing.version;
2414
- const publisherDid = existing.registryPublisherDid;
2415
- const slug = existing.registrySlug;
2416
- const { DiscoveryClient, registryLabelerPolicy } = await import("@emdash-cms/registry-client/discovery");
2417
- const aggregatorDeadline = Date.now() + AGGREGATOR_TOTAL_BUDGET_MS;
2418
- const discovery = new DiscoveryClient({
2419
- aggregatorUrl: registryConfig.aggregatorUrl,
2420
- acceptLabelers: registryConfig.acceptLabelers,
2421
- labelerPolicy: registryLabelerPolicy(registryConfig.acceptLabelers),
2422
- fetch: timedFetch(aggregatorDeadline)
2423
- });
2424
- const MAX_LIST_PAGES = 20;
2425
- const releaseView = await (async () => {
2426
- if (!opts?.version) return discovery.getLatestRelease({
2427
- did: publisherDid,
2428
- package: slug
2429
- });
2430
- let cursor;
2431
- const seenCursors = /* @__PURE__ */ new Set();
2432
- for (let page = 0; page < MAX_LIST_PAGES; page++) {
2433
- if (cursor !== void 0) {
2434
- if (seenCursors.has(cursor)) break;
2435
- seenCursors.add(cursor);
2436
- }
2437
- const result = await discovery.listReleases({
2438
- did: publisherDid,
2439
- package: slug,
2440
- cursor,
2441
- limit: 50
2442
- });
2443
- for (const r of result.releases) if (r.version === opts.version) return r;
2444
- if (!result.cursor) break;
2445
- cursor = result.cursor;
2446
- }
2447
- })();
2448
- if (!releaseView) return {
2449
- success: false,
2450
- error: {
2451
- code: "NO_VERSION",
2452
- message: opts?.version ? `Version ${opts.version} not found for ${publisherDid}/${slug}` : `No installable release found for ${publisherDid}/${slug}`
2453
- }
2454
- };
2455
- const signedRelease = releaseView.release;
2456
- if (releaseView.did !== publisherDid || releaseView.package !== slug || signedRelease?.package !== slug || opts?.version !== void 0 && releaseView.version !== opts.version || signedRelease?.version !== releaseView.version) return {
2457
- success: false,
2458
- error: {
2459
- code: "AGGREGATOR_IDENTITY_MISMATCH",
2460
- message: "Aggregator returned a release view that does not match the requested package or version."
2461
- }
2462
- };
2463
- const newVersion = releaseView.version;
2464
- if (evaluateRegistryReleaseWithdrawal(releaseView, discovery.labelerPolicy).withdrawn) return {
2465
- success: false,
2466
- error: {
2467
- code: "YANKED",
2468
- message: "Release has been withdrawn"
2469
- }
2470
- };
2471
- if (newVersion === oldVersion) return {
2472
- success: false,
2473
- error: {
2474
- code: "ALREADY_UP_TO_DATE",
2475
- message: "Plugin is already at the requested version"
2476
- }
2477
- };
2478
- if (opts?.hostEnv) {
2479
- const envError = assertEnvCompatible(signedRelease.requires, opts.hostEnv);
2480
- if (envError) return {
2481
- success: false,
2482
- error: envError
2483
- };
2484
- }
2485
- const packageArtifact = signedRelease.artifacts?.package;
2486
- const declaredChecksum = packageArtifact?.checksum;
2487
- if (!packageArtifact || !declaredChecksum) return {
2488
- success: false,
2489
- error: {
2490
- code: "INVALID_RELEASE",
2491
- message: "Release record is missing its package artifact or checksum"
2492
- }
2493
- };
2494
- const artifactBytes = await fetchArtifact(releaseView.artifactCaches ?? [], packageArtifact, {
2495
- did: publisherDid,
2496
- collection: NSID.packageRelease,
2497
- rkey: `${slug}:${newVersion}`,
2498
- cid: releaseView.cid
2499
- }, signedRelease.auth);
2500
- if (!await verifyChecksum(artifactBytes, declaredChecksum)) return {
2501
- success: false,
2502
- error: {
2503
- code: "CHECKSUM_MISMATCH",
2504
- message: "Artifact bytes do not match the release's published checksum"
2505
- }
2506
- };
2507
- const bundle = await extractBundle(artifactBytes);
2508
- if (bundle.manifest.version !== newVersion) return {
2509
- success: false,
2510
- error: {
2511
- code: "BUNDLE_VERSION_MISMATCH",
2512
- message: `Bundle manifest version (${bundle.manifest.version}) does not match release version (${newVersion})`
2513
- }
2514
- };
2515
- if (bundle.manifest.id !== slug) return {
2516
- success: false,
2517
- error: {
2518
- code: "BUNDLE_IDENTITY_MISMATCH",
2519
- message: `Bundle manifest id (${bundle.manifest.id}) does not match registry slug (${slug})`
2520
- }
2521
- };
2522
- bundle.manifest = {
2523
- ...bundle.manifest,
2524
- id: pluginId
2525
- };
2526
- const recordExt = (signedRelease?.extensions)?.[RELEASE_EXTENSION_NSID];
2527
- if (!enforcedAccessEqual(recordExt?.declaredAccess ?? {}, bundle.manifest.declaredAccess ?? {})) return {
2528
- success: false,
2529
- error: {
2530
- code: "DECLARED_ACCESS_DRIFT",
2531
- message: "The plugin bundle declares different permissions than its published record. Update refused."
2532
- }
2533
- };
2534
- const oldBundle = await loadBundleFromR2(storage, pluginId, oldVersion, "registry");
2535
- const capabilityChanges = diffCapabilities(oldBundle?.manifest.capabilities ?? [], bundle.manifest.capabilities);
2536
- if (capabilityChanges.added.length > 0 && !opts?.confirmCapabilityChanges) return {
2537
- success: false,
2538
- error: {
2539
- code: "CAPABILITY_ESCALATION",
2540
- message: "Plugin update requires new capabilities",
2541
- details: { capabilityChanges }
2542
- }
2543
- };
2544
- const routeVisibilityChanges = diffRouteVisibility(oldBundle?.manifest, bundle.manifest);
2545
- const hasNewPublicRoutes = routeVisibilityChanges.newlyPublic.length > 0;
2546
- if (hasNewPublicRoutes && !opts?.confirmRouteVisibilityChanges) return {
2547
- success: false,
2548
- error: {
2549
- code: "ROUTE_VISIBILITY_ESCALATION",
2550
- message: "Plugin update exposes new public (unauthenticated) routes",
2551
- details: {
2552
- routeVisibilityChanges,
2553
- capabilityChanges
2554
- }
2555
- }
2556
- };
2557
- const oldMcpTools = [...oldBundle?.manifest.mcp?.tools ?? []].toSorted((a, b) => a.name.localeCompare(b.name));
2558
- const newMcpTools = [...bundle.manifest.mcp?.tools ?? []].toSorted((a, b) => a.name.localeCompare(b.name));
2559
- if (JSON.stringify(oldMcpTools) !== JSON.stringify(newMcpTools) && !opts?.confirmMcpTools) return {
2560
- success: false,
2561
- error: {
2562
- code: "MCP_TOOL_CONSENT_REQUIRED",
2563
- message: "Plugin update changes its MCP tools",
2564
- details: { mcpTools: newMcpTools.map(({ inputSchema: _, outputSchema: __, ...tool }) => tool) }
2565
- }
2566
- };
2567
- await storeBundleInR2(storage, pluginId, newVersion, bundle, "registry");
2568
- await stateRepo.upsert(pluginId, newVersion, "active", {
2569
- source: "registry",
2570
- registryPublisherDid: publisherDid,
2571
- registrySlug: slug,
2572
- displayName: existing.displayName ?? slug,
2573
- description: existing.description ?? void 0,
2574
- mcpToolsEnabled: false,
2575
- mcpToolsConsent: null
2576
- });
2577
- await syncDeclaredStorageIndexes(db, [bundle.manifest]);
2578
- deleteBundleFromR2(storage, pluginId, oldVersion, "registry").catch(() => {});
2579
- return {
2580
- success: true,
2581
- data: {
2582
- pluginId,
2583
- oldVersion,
2584
- newVersion,
2585
- capabilityChanges,
2586
- routeVisibilityChanges: hasNewPublicRoutes ? routeVisibilityChanges : void 0
2587
- }
2588
- };
2589
- } catch (err) {
2590
- if (err instanceof ClientValidationError) return {
2591
- success: false,
2592
- error: {
2593
- code: "AGGREGATOR_RESPONSE_INVALID",
2594
- message: `Aggregator returned a response that does not conform to its lexicon (${err.target})`
2595
- }
2596
- };
2597
- if (err instanceof ClientResponseError) {
2598
- if (err.error === "ListingUnavailable") return {
2599
- success: false,
2600
- error: {
2601
- code: "LISTING_UNAVAILABLE",
2602
- message: "This plugin is unavailable under the active registry policy"
2603
- }
2604
- };
2605
- return {
2606
- success: false,
2607
- error: {
2608
- code: err.status === 404 ? "AGGREGATOR_NOT_FOUND" : "AGGREGATOR_HTTP_ERROR",
2609
- message: `Aggregator returned ${err.status}: ${err.error}`
2610
- }
2611
- };
2612
- }
2613
- if (err instanceof EmDashStorageError) return {
2614
- success: false,
2615
- error: {
2616
- code: err.code ?? "STORAGE_ERROR",
2617
- message: "Storage error while updating plugin"
2618
- }
2619
- };
2620
- console.error("[registry-update] Failed:", err);
2621
- return {
2622
- success: false,
2623
- error: {
2624
- code: "UPDATE_FAILED",
2625
- message: err instanceof Error ? err.message : "Failed to update plugin"
2626
- }
2627
- };
2628
- }
2629
- }
2630
- /**
2631
- * Bulk update check across every installed registry plugin. Queries the
2632
- * aggregator for each plugin's latest release and reports `hasUpdate`
2633
- * based on the version comparison. Plugins whose aggregator lookup fails
2634
- * (unreachable, delisted, malformed) are skipped silently — one bad
2635
- * publisher must not blank the whole admin Updates list.
2636
- */
2637
- async function handleRegistryUpdateCheck(db, registryConfigInput) {
2638
- const registryConfig = coerceRegistryConfig(registryConfigInput);
2639
- if (!registryConfig) return {
2640
- success: false,
2641
- error: {
2642
- code: "REGISTRY_NOT_CONFIGURED",
2643
- message: "Registry is not configured"
2644
- }
2645
- };
2646
- try {
2647
- const registryPlugins = await new PluginStateRepository(db).getRegistryPlugins();
2648
- if (registryPlugins.length === 0) return {
2649
- success: true,
2650
- data: { items: [] }
2651
- };
2652
- const { DiscoveryClient, registryLabelerPolicy } = await import("@emdash-cms/registry-client/discovery");
2653
- const aggregatorDeadline = Date.now() + AGGREGATOR_TOTAL_BUDGET_MS;
2654
- const discovery = new DiscoveryClient({
2655
- aggregatorUrl: registryConfig.aggregatorUrl,
2656
- acceptLabelers: registryConfig.acceptLabelers,
2657
- labelerPolicy: registryLabelerPolicy(registryConfig.acceptLabelers),
2658
- fetch: timedFetch(aggregatorDeadline)
2659
- });
2660
- const items = [];
2661
- for (const plugin of registryPlugins) {
2662
- if (!plugin.registryPublisherDid || !plugin.registrySlug) continue;
2663
- try {
2664
- const releaseView = await discovery.getLatestRelease({
2665
- did: plugin.registryPublisherDid,
2666
- package: plugin.registrySlug
2667
- });
2668
- if (evaluateRegistryReleaseWithdrawal(releaseView, discovery.labelerPolicy).withdrawn) continue;
2669
- const latest = releaseView.version;
2670
- if (!latest) continue;
2671
- const installed = plugin.version;
2672
- items.push({
2673
- pluginId: plugin.pluginId,
2674
- installed,
2675
- latest,
2676
- hasUpdate: latest !== installed,
2677
- hasCapabilityChanges: false,
2678
- hasRouteVisibilityChanges: false
2679
- });
2680
- } catch (err) {
2681
- console.warn(`[registry-update-check] Skipped ${plugin.pluginId}:`, err);
2682
- }
2683
- }
2684
- return {
2685
- success: true,
2686
- data: { items }
2687
- };
2688
- } catch (err) {
2689
- if (err instanceof ClientValidationError) return {
2690
- success: false,
2691
- error: {
2692
- code: "AGGREGATOR_RESPONSE_INVALID",
2693
- message: `Aggregator returned a response that does not conform to its lexicon (${err.target})`
2694
- }
2695
- };
2696
- if (err instanceof ClientResponseError) return {
2697
- success: false,
2698
- error: {
2699
- code: err.status === 404 ? "AGGREGATOR_NOT_FOUND" : "AGGREGATOR_HTTP_ERROR",
2700
- message: `Aggregator returned ${err.status}: ${err.error}`
2701
- }
2702
- };
2703
- console.error("[registry-update-check] Failed:", err);
2704
- return {
2705
- success: false,
2706
- error: {
2707
- code: "UPDATE_CHECK_FAILED",
2708
- message: "Failed to check for registry updates"
2709
- }
2710
- };
2711
- }
2712
- }
2713
-
2714
- //#endregion
2715
- export { handleThemeSearch as _, verifyChecksum as a, validateAggregatorUrl as c, handleMarketplaceInstall as d, handleMarketplaceSearch as f, handleThemeGetDetail as g, handleMarketplaceUpdateCheck as h, handleRegistryUpdateCheck as i, fetchRegistryArtifactUrl as l, handleMarketplaceUpdate as m, handleRegistryUninstall as n, coerceRegistryConfig as o, handleMarketplaceUninstall as p, handleRegistryUpdate as r, normalizeRegistryConfig as s, handleRegistryInstall as t, handleMarketplaceGetPlugin as u, loadBundleFromR2 as v, syncDeclaredStorageIndexes as y };
2716
- //# sourceMappingURL=registry-BiO5WwPq.mjs.map