emdash 0.8.0 → 0.10.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 (317) hide show
  1. package/dist/{adapters-BKSf3T9R.d.mts → adapters-BktHA7EO.d.mts} +1 -1
  2. package/dist/{adapters-BKSf3T9R.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
  3. package/dist/{apply-x0eMK1lX.mjs → apply-UsrFuO7l.mjs} +207 -355
  4. package/dist/apply-UsrFuO7l.mjs.map +1 -0
  5. package/dist/astro/index.d.mts +6 -6
  6. package/dist/astro/index.d.mts.map +1 -1
  7. package/dist/astro/index.mjs +118 -4
  8. package/dist/astro/index.mjs.map +1 -1
  9. package/dist/astro/middleware/auth.d.mts +6 -7
  10. package/dist/astro/middleware/auth.d.mts.map +1 -1
  11. package/dist/astro/middleware/auth.mjs +14 -57
  12. package/dist/astro/middleware/auth.mjs.map +1 -1
  13. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  14. package/dist/astro/middleware/redirect.mjs +15 -10
  15. package/dist/astro/middleware/redirect.mjs.map +1 -1
  16. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  17. package/dist/astro/middleware/request-context.mjs +8 -5
  18. package/dist/astro/middleware/request-context.mjs.map +1 -1
  19. package/dist/astro/middleware/setup.mjs +1 -1
  20. package/dist/astro/middleware.d.mts.map +1 -1
  21. package/dist/astro/middleware.mjs +70 -121
  22. package/dist/astro/middleware.mjs.map +1 -1
  23. package/dist/astro/types.d.mts +25 -10
  24. package/dist/astro/types.d.mts.map +1 -1
  25. package/dist/{byline-Chbr2GoP.mjs → byline-C3vnhIpU.mjs} +4 -4
  26. package/dist/{byline-Chbr2GoP.mjs.map → byline-C3vnhIpU.mjs.map} +1 -1
  27. package/dist/bylines-esI7ioa9.mjs +113 -0
  28. package/dist/bylines-esI7ioa9.mjs.map +1 -0
  29. package/dist/cache-fTzxgMFJ.mjs +65 -0
  30. package/dist/cache-fTzxgMFJ.mjs.map +1 -0
  31. package/dist/{chunks-HGz06Soa.mjs → chunks-Da2-b-oA.mjs} +8 -2
  32. package/dist/{chunks-HGz06Soa.mjs.map → chunks-Da2-b-oA.mjs.map} +1 -1
  33. package/dist/cli/index.mjs +456 -90
  34. package/dist/cli/index.mjs.map +1 -1
  35. package/dist/client/cf-access.d.mts +1 -1
  36. package/dist/client/index.d.mts +1 -1
  37. package/dist/client/index.mjs +3 -3
  38. package/dist/client/index.mjs.map +1 -1
  39. package/dist/{config-BXwuX8Bx.mjs → config-CVssduLe.mjs} +1 -1
  40. package/dist/{config-BXwuX8Bx.mjs.map → config-CVssduLe.mjs.map} +1 -1
  41. package/dist/{content-BcQPYxdV.mjs → content-C7G4QXkK.mjs} +42 -14
  42. package/dist/content-C7G4QXkK.mjs.map +1 -0
  43. package/dist/db/index.d.mts +3 -3
  44. package/dist/db/index.mjs +2 -2
  45. package/dist/db/libsql.d.mts +1 -1
  46. package/dist/db/libsql.d.mts.map +1 -1
  47. package/dist/db/libsql.mjs +7 -2
  48. package/dist/db/libsql.mjs.map +1 -1
  49. package/dist/db/postgres.d.mts +1 -1
  50. package/dist/db/sqlite.d.mts +1 -1
  51. package/dist/db/sqlite.d.mts.map +1 -1
  52. package/dist/db/sqlite.mjs +8 -3
  53. package/dist/db/sqlite.mjs.map +1 -1
  54. package/dist/{db-errors-l1Qh2RPR.mjs → db-errors-B7P2pSCn.mjs} +1 -1
  55. package/dist/{db-errors-l1Qh2RPR.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
  56. package/dist/{default-DCVqE5ib.mjs → default-pHuz9WF6.mjs} +1 -1
  57. package/dist/{default-DCVqE5ib.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
  58. package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
  59. package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
  60. package/dist/{error-zG5T1UGA.mjs → error-DqnRMM5z.mjs} +1 -1
  61. package/dist/{error-zG5T1UGA.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
  62. package/dist/{index-DIb-CzNx.d.mts → index-DjPMOfO0.d.mts} +162 -87
  63. package/dist/index-DjPMOfO0.d.mts.map +1 -0
  64. package/dist/index.d.mts +11 -11
  65. package/dist/index.mjs +27 -24
  66. package/dist/{load-CyEoextb.mjs → load-sXRuM7Us.mjs} +2 -2
  67. package/dist/{load-CyEoextb.mjs.map → load-sXRuM7Us.mjs.map} +1 -1
  68. package/dist/{loader-CndGj8kM.mjs → loader-Bx2_9-5e.mjs} +53 -8
  69. package/dist/loader-Bx2_9-5e.mjs.map +1 -0
  70. package/dist/{manifest-schema-DH9xhc6t.mjs → manifest-schema-CXAbd1vH.mjs} +33 -3
  71. package/dist/manifest-schema-CXAbd1vH.mjs.map +1 -0
  72. package/dist/media/index.d.mts +1 -1
  73. package/dist/media/index.mjs +1 -1
  74. package/dist/media/local-runtime.d.mts +7 -7
  75. package/dist/{mode-BnAOqItE.mjs → mode-YhqNVef_.mjs} +1 -1
  76. package/dist/{mode-BnAOqItE.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
  77. package/dist/options-nPxWnrya.mjs +117 -0
  78. package/dist/options-nPxWnrya.mjs.map +1 -0
  79. package/dist/page/index.d.mts +2 -2
  80. package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
  81. package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
  82. package/dist/{placeholder-D29tWZ7o.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
  83. package/dist/{placeholder-D29tWZ7o.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
  84. package/dist/{placeholder-C-fk5hYI.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
  85. package/dist/{placeholder-C-fk5hYI.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
  86. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  87. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  88. package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
  89. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  90. package/dist/public-url-B1AxbbbQ.mjs +51 -0
  91. package/dist/public-url-B1AxbbbQ.mjs.map +1 -0
  92. package/dist/{query-fqEdLFms.mjs → query-Bo-msrmu.mjs} +114 -16
  93. package/dist/query-Bo-msrmu.mjs.map +1 -0
  94. package/dist/{redirect-D_pshWdf.mjs → redirect-C5H7VGIX.mjs} +11 -6
  95. package/dist/redirect-C5H7VGIX.mjs.map +1 -0
  96. package/dist/{registry-C3Mr0ODu.mjs → registry-Beb7wxFc.mjs} +39 -5
  97. package/dist/registry-Beb7wxFc.mjs.map +1 -0
  98. package/dist/{request-cache-Ci7f5pBb.mjs → request-cache-C-tIpYIw.mjs} +1 -1
  99. package/dist/{request-cache-Ci7f5pBb.mjs.map → request-cache-C-tIpYIw.mjs.map} +1 -1
  100. package/dist/runner-Clwe4Mme.d.mts +44 -0
  101. package/dist/runner-Clwe4Mme.d.mts.map +1 -0
  102. package/dist/{runner-tQ7BJ4T7.mjs → runner-DMnlIkh4.mjs} +616 -191
  103. package/dist/runner-DMnlIkh4.mjs.map +1 -0
  104. package/dist/runtime.d.mts +6 -6
  105. package/dist/runtime.mjs +2 -2
  106. package/dist/{search-BoZYFuUk.mjs → search-DkN-BqsS.mjs} +270 -152
  107. package/dist/search-DkN-BqsS.mjs.map +1 -0
  108. package/dist/secrets-CZ8rxLX3.mjs +314 -0
  109. package/dist/secrets-CZ8rxLX3.mjs.map +1 -0
  110. package/dist/seed/index.d.mts +2 -2
  111. package/dist/seed/index.mjs +13 -11
  112. package/dist/seo/index.d.mts +1 -1
  113. package/dist/storage/local.d.mts +1 -1
  114. package/dist/storage/local.mjs +1 -1
  115. package/dist/storage/s3.d.mts +1 -1
  116. package/dist/storage/s3.mjs +1 -1
  117. package/dist/taxonomies-CTtewrSQ.mjs +407 -0
  118. package/dist/taxonomies-CTtewrSQ.mjs.map +1 -0
  119. package/dist/taxonomy-DSxx2K2L.mjs +218 -0
  120. package/dist/taxonomy-DSxx2K2L.mjs.map +1 -0
  121. package/dist/{tokens-D9vnZqYS.mjs → tokens-CyRDPVW2.mjs} +1 -1
  122. package/dist/{tokens-D9vnZqYS.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
  123. package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
  124. package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
  125. package/dist/{transport-CUnEL3Vs.d.mts → transport-DX_5rpsq.d.mts} +1 -1
  126. package/dist/{transport-CUnEL3Vs.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
  127. package/dist/{transport-C9ugt2Nr.mjs → transport-xpzIjCIB.mjs} +6 -5
  128. package/dist/{transport-C9ugt2Nr.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
  129. package/dist/{types-BrA0xf5I.d.mts → types-B_CXXnzh.d.mts} +1 -1
  130. package/dist/{types-BrA0xf5I.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
  131. package/dist/{types-DIMwPFub.d.mts → types-C-aFbqmA.d.mts} +1 -1
  132. package/dist/{types-DIMwPFub.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
  133. package/dist/types-CoO6mpV3.mjs +68 -0
  134. package/dist/types-CoO6mpV3.mjs.map +1 -0
  135. package/dist/{types-i36XcA_X.d.mts → types-D19uBYWn.d.mts} +83 -7
  136. package/dist/types-D19uBYWn.d.mts.map +1 -0
  137. package/dist/{types-BmPPSUEx.d.mts → types-Dl1fgFjn.d.mts} +24 -2
  138. package/dist/{types-BmPPSUEx.d.mts.map → types-Dl1fgFjn.d.mts.map} +1 -1
  139. package/dist/{types-CS8FIX7L.d.mts → types-Dtx1mSMX.d.mts} +9 -1
  140. package/dist/types-Dtx1mSMX.d.mts.map +1 -0
  141. package/dist/{types-Bm1dn-q3.mjs → types-Eg829jj9.mjs} +1 -1
  142. package/dist/{types-Bm1dn-q3.mjs.map → types-Eg829jj9.mjs.map} +1 -1
  143. package/dist/{types-CgqmmMJB.mjs → types-K-EkEQCI.mjs} +1 -1
  144. package/dist/{types-CgqmmMJB.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
  145. package/dist/{validate-CxVsLehf.mjs → validate-CBIbxM3L.mjs} +14 -10
  146. package/dist/validate-CBIbxM3L.mjs.map +1 -0
  147. package/dist/{validate-DHxmpFJt.d.mts → validate-DHGwADqO.d.mts} +18 -5
  148. package/dist/validate-DHGwADqO.d.mts.map +1 -0
  149. package/dist/{validation-C-ZpN2GI.mjs → validation-B1NYiEos.mjs} +6 -6
  150. package/dist/{validation-C-ZpN2GI.mjs.map → validation-B1NYiEos.mjs.map} +1 -1
  151. package/dist/version-CMD42IRC.mjs +7 -0
  152. package/dist/{version-Bbq8TCrz.mjs.map → version-CMD42IRC.mjs.map} +1 -1
  153. package/dist/{zod-generator-CpwccCIv.mjs → zod-generator-BNJDQBSZ.mjs} +11 -6
  154. package/dist/{zod-generator-CpwccCIv.mjs.map → zod-generator-BNJDQBSZ.mjs.map} +1 -1
  155. package/locals.d.ts +1 -6
  156. package/package.json +9 -8
  157. package/src/api/handlers/comments.ts +6 -4
  158. package/src/api/handlers/content.ts +40 -1
  159. package/src/api/handlers/dashboard.ts +29 -36
  160. package/src/api/handlers/device-flow.ts +5 -0
  161. package/src/api/handlers/marketplace.ts +11 -4
  162. package/src/api/handlers/menus.ts +256 -75
  163. package/src/api/handlers/oauth-authorization.ts +72 -33
  164. package/src/api/handlers/revision.ts +23 -14
  165. package/src/api/handlers/taxonomies.ts +273 -100
  166. package/src/api/public-url.ts +48 -2
  167. package/src/api/schemas/comments.ts +2 -2
  168. package/src/api/schemas/common.ts +7 -0
  169. package/src/api/schemas/content.ts +17 -0
  170. package/src/api/schemas/menus.ts +23 -0
  171. package/src/api/schemas/sections.ts +3 -3
  172. package/src/api/schemas/taxonomies.ts +39 -0
  173. package/src/api/schemas/users.ts +1 -1
  174. package/src/api/types.ts +5 -1
  175. package/src/astro/integration/index.ts +17 -0
  176. package/src/astro/integration/routes.ts +10 -0
  177. package/src/astro/integration/runtime.ts +30 -0
  178. package/src/astro/integration/virtual-modules.ts +32 -2
  179. package/src/astro/integration/vite-config.ts +6 -1
  180. package/src/astro/middleware/auth.ts +13 -6
  181. package/src/astro/middleware/redirect.ts +29 -16
  182. package/src/astro/middleware/request-context.ts +15 -5
  183. package/src/astro/middleware.ts +23 -9
  184. package/src/astro/routes/api/auth/invite/complete.ts +6 -1
  185. package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
  186. package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
  187. package/src/astro/routes/api/auth/signup/complete.ts +6 -1
  188. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
  189. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  190. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
  191. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
  192. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
  193. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
  194. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
  195. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
  196. package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
  197. package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
  198. package/src/astro/routes/api/import/wordpress/prepare.ts +7 -8
  199. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
  200. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
  201. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
  202. package/src/astro/routes/api/manifest.ts +62 -45
  203. package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
  204. package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
  205. package/src/astro/routes/api/menus/[name]/items.ts +16 -6
  206. package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
  207. package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
  208. package/src/astro/routes/api/menus/[name].ts +19 -10
  209. package/src/astro/routes/api/menus/index.ts +9 -6
  210. package/src/astro/routes/api/openapi.json.ts +27 -10
  211. package/src/astro/routes/api/redirects/404s/index.ts +10 -4
  212. package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
  213. package/src/astro/routes/api/redirects/[id].ts +10 -4
  214. package/src/astro/routes/api/redirects/index.ts +7 -3
  215. package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
  216. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
  217. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
  218. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
  219. package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
  220. package/src/astro/routes/api/schema/collections/index.ts +1 -1
  221. package/src/astro/routes/api/search/index.ts +10 -2
  222. package/src/astro/routes/api/sections/[slug].ts +10 -4
  223. package/src/astro/routes/api/sections/index.ts +7 -3
  224. package/src/astro/routes/api/setup/admin-verify.ts +6 -1
  225. package/src/astro/routes/api/snapshot.ts +44 -18
  226. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
  227. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
  228. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
  229. package/src/astro/routes/api/taxonomies/index.ts +9 -7
  230. package/src/astro/routes/api/themes/preview.ts +11 -5
  231. package/src/astro/types.ts +23 -3
  232. package/src/auth/allowed-origins.ts +168 -0
  233. package/src/auth/passkey-config.ts +35 -13
  234. package/src/bylines/index.ts +37 -88
  235. package/src/cli/commands/auth.ts +28 -6
  236. package/src/cli/commands/bundle-utils.ts +11 -2
  237. package/src/cli/commands/bundle.ts +28 -8
  238. package/src/cli/commands/content.ts +13 -0
  239. package/src/cli/commands/export-seed.ts +82 -21
  240. package/src/cli/commands/login.ts +8 -1
  241. package/src/cli/commands/plugin-init.ts +216 -90
  242. package/src/cli/commands/publish.ts +24 -0
  243. package/src/cli/commands/secrets.ts +183 -0
  244. package/src/cli/credentials.ts +1 -1
  245. package/src/cli/index.ts +5 -1
  246. package/src/client/index.ts +4 -4
  247. package/src/client/transport.ts +17 -7
  248. package/src/components/Break.astro +2 -2
  249. package/src/components/EmDashHead.astro +18 -13
  250. package/src/components/Embed.astro +1 -1
  251. package/src/components/Gallery.astro +1 -1
  252. package/src/components/Image.astro +1 -1
  253. package/src/components/InlinePortableTextEditor.tsx +104 -18
  254. package/src/config/secrets.ts +528 -0
  255. package/src/database/dialect-helpers.ts +50 -0
  256. package/src/database/migrations/034_published_at_index.ts +1 -1
  257. package/src/database/migrations/035_bounded_404_log.ts +56 -39
  258. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
  259. package/src/database/migrations/runner.ts +158 -23
  260. package/src/database/repositories/content.ts +47 -12
  261. package/src/database/repositories/redirect.ts +14 -3
  262. package/src/database/repositories/taxonomy.ts +212 -82
  263. package/src/database/types.ts +10 -2
  264. package/src/db/libsql.ts +1 -3
  265. package/src/db/sqlite.ts +2 -5
  266. package/src/emdash-runtime.ts +84 -159
  267. package/src/i18n/resolve.ts +37 -0
  268. package/src/index.ts +9 -0
  269. package/src/loader.ts +73 -3
  270. package/src/mcp/server.ts +180 -54
  271. package/src/menus/index.ts +143 -124
  272. package/src/menus/types.ts +15 -1
  273. package/src/page/site-identity.ts +58 -0
  274. package/src/plugins/adapt-sandbox-entry.ts +22 -10
  275. package/src/plugins/context.ts +13 -10
  276. package/src/plugins/define-plugin.ts +40 -12
  277. package/src/plugins/hooks.ts +23 -19
  278. package/src/plugins/index.ts +9 -0
  279. package/src/plugins/manifest-schema.ts +37 -2
  280. package/src/plugins/types.ts +151 -11
  281. package/src/preview/urls.ts +23 -3
  282. package/src/query.ts +148 -5
  283. package/src/redirects/cache.ts +38 -18
  284. package/src/schema/registry.ts +56 -0
  285. package/src/schema/zod-generator.ts +39 -7
  286. package/src/seed/apply.ts +142 -54
  287. package/src/seed/types.ts +14 -1
  288. package/src/seed/validate.ts +27 -13
  289. package/src/settings/index.ts +80 -6
  290. package/src/settings/types.ts +23 -1
  291. package/src/taxonomies/index.ts +237 -210
  292. package/src/taxonomies/types.ts +10 -0
  293. package/dist/apply-x0eMK1lX.mjs.map +0 -1
  294. package/dist/bylines-CRNsVG88.mjs +0 -157
  295. package/dist/bylines-CRNsVG88.mjs.map +0 -1
  296. package/dist/cache-BkKBuIvS.mjs +0 -56
  297. package/dist/cache-BkKBuIvS.mjs.map +0 -1
  298. package/dist/chunk-ClPoSABd.mjs +0 -21
  299. package/dist/content-BcQPYxdV.mjs.map +0 -1
  300. package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
  301. package/dist/index-DIb-CzNx.d.mts.map +0 -1
  302. package/dist/loader-CndGj8kM.mjs.map +0 -1
  303. package/dist/manifest-schema-DH9xhc6t.mjs.map +0 -1
  304. package/dist/query-fqEdLFms.mjs.map +0 -1
  305. package/dist/redirect-D_pshWdf.mjs.map +0 -1
  306. package/dist/registry-C3Mr0ODu.mjs.map +0 -1
  307. package/dist/runner-OURCaApa.d.mts +0 -34
  308. package/dist/runner-OURCaApa.d.mts.map +0 -1
  309. package/dist/runner-tQ7BJ4T7.mjs.map +0 -1
  310. package/dist/search-BoZYFuUk.mjs.map +0 -1
  311. package/dist/taxonomies-B4IAshV8.mjs +0 -308
  312. package/dist/taxonomies-B4IAshV8.mjs.map +0 -1
  313. package/dist/types-CS8FIX7L.d.mts.map +0 -1
  314. package/dist/types-i36XcA_X.d.mts.map +0 -1
  315. package/dist/validate-CxVsLehf.mjs.map +0 -1
  316. package/dist/validate-DHxmpFJt.d.mts.map +0 -1
  317. package/dist/version-Bbq8TCrz.mjs +0 -7
@@ -1,23 +1,26 @@
1
1
  import { n as validateJsonFieldName, r as validatePluginIdentifier, t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
2
- import { o as jsonExtractExpr } from "./dialect-helpers-DhTzaUxP.mjs";
3
- import { a as slugify, r as RevisionRepository, t as ContentRepository } from "./content-BcQPYxdV.mjs";
2
+ import { s as jsonExtractExpr } from "./dialect-helpers-BKCvISIQ.mjs";
3
+ import { r as isI18nEnabled } from "./config-CVssduLe.mjs";
4
+ import { a as slugify, r as RevisionRepository, t as ContentRepository } from "./content-C7G4QXkK.mjs";
4
5
  import { r as encodeBase64, t as decodeBase64 } from "./base64-MBPo9ozB.mjs";
5
6
  import { i as encodeCursor, n as InvalidCursorError, r as decodeCursor, t as EmDashValidationError } from "./types-BIgulNsW.mjs";
6
7
  import { t as MediaRepository } from "./media-D8FbNsl0.mjs";
7
- import { a as ssrfSafeFetch, i as resolveAndValidateExternalUrl, o as stripCredentialHeaders, p as OptionsRepository, r as SsrfError, s as validateExternalUrl } from "./apply-x0eMK1lX.mjs";
8
- import { t as withTransaction } from "./transaction-Cn2rjY78.mjs";
9
- import { t as RedirectRepository } from "./redirect-D_pshWdf.mjs";
10
- import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-HGz06Soa.mjs";
11
- import { t as BylineRepository } from "./byline-Chbr2GoP.mjs";
12
- import { r as isI18nEnabled } from "./config-BXwuX8Bx.mjs";
13
- import { r as invalidateRedirectCache } from "./cache-BkKBuIvS.mjs";
14
- import { t as isMissingTableError } from "./db-errors-l1Qh2RPR.mjs";
15
- import { r as hashString } from "./zod-generator-CpwccCIv.mjs";
16
- import { i as FTSManager, n as SchemaRegistry } from "./registry-C3Mr0ODu.mjs";
17
- import { n as getDb } from "./loader-CndGj8kM.mjs";
18
- import { n as requestCached } from "./request-cache-Ci7f5pBb.mjs";
19
- import { i as pluginManifestSchema } from "./manifest-schema-DH9xhc6t.mjs";
20
- import { t as generatePreviewToken } from "./tokens-D9vnZqYS.mjs";
8
+ import { t as OptionsRepository } from "./options-nPxWnrya.mjs";
9
+ import { t as withTransaction } from "./transaction-D44LBXvU.mjs";
10
+ import { t as RedirectRepository } from "./redirect-C5H7VGIX.mjs";
11
+ import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-Da2-b-oA.mjs";
12
+ import { t as BylineRepository } from "./byline-C3vnhIpU.mjs";
13
+ import { r as invalidateRedirectCache } from "./cache-fTzxgMFJ.mjs";
14
+ import { t as isMissingTableError } from "./db-errors-B7P2pSCn.mjs";
15
+ import { r as hashString } from "./zod-generator-BNJDQBSZ.mjs";
16
+ import { i as FTSManager, n as SchemaRegistry } from "./registry-Beb7wxFc.mjs";
17
+ import { r as getDb } from "./loader-Bx2_9-5e.mjs";
18
+ import { n as requestCached } from "./request-cache-C-tIpYIw.mjs";
19
+ import { a as ssrfSafeFetch, i as resolveAndValidateExternalUrl, o as stripCredentialHeaders, r as SsrfError, s as validateExternalUrl } from "./apply-UsrFuO7l.mjs";
20
+ import { d as resolveLocale, f as resolveLocaleChain } from "./taxonomies-CTtewrSQ.mjs";
21
+ import { i as pluginManifestSchema } from "./manifest-schema-CXAbd1vH.mjs";
22
+ import { i as normalizeCapabilities } from "./types-CoO6mpV3.mjs";
23
+ import { t as generatePreviewToken } from "./tokens-CyRDPVW2.mjs";
21
24
  import { sql } from "kysely";
22
25
  import { AsyncLocalStorage } from "node:async_hooks";
23
26
  import { ulid } from "ulidx";
@@ -1276,6 +1279,10 @@ async function handleContentCreate(db, collection, body) {
1276
1279
  created.primaryBylineId = body.bylines[0]?.bylineId ?? null;
1277
1280
  }
1278
1281
  await hydrateBylines(trx, collection, created);
1282
+ if (body.translationOf) {
1283
+ const { TaxonomyRepository } = await import("./taxonomy-DSxx2K2L.mjs").then((n) => n.n);
1284
+ await new TaxonomyRepository(trx).copyEntryTerms(collection, body.translationOf, created.id);
1285
+ }
1279
1286
  if (body.seo && hasSeo) created.seo = await new SeoRepository(trx).upsert(collection, created.id, body.seo);
1280
1287
  else if (hasSeo) created.seo = { ...SEO_DEFAULTS };
1281
1288
  return created;
@@ -1697,6 +1704,13 @@ async function handleContentUnschedule(db, collection, id) {
1697
1704
  data: { item }
1698
1705
  };
1699
1706
  } catch (error) {
1707
+ if (error instanceof EmDashValidationError) return {
1708
+ success: false,
1709
+ error: {
1710
+ code: "VALIDATION_ERROR",
1711
+ message: error.message
1712
+ }
1713
+ };
1700
1714
  console.error("Content unschedule error:", error);
1701
1715
  return {
1702
1716
  success: false,
@@ -1714,12 +1728,12 @@ async function handleContentUnschedule(db, collection, id) {
1714
1728
  * (syncDataColumns, slug sync, status/revision update) that must
1715
1729
  * be atomic to prevent FTS shadow table corruption on crash.
1716
1730
  */
1717
- async function handleContentPublish(db, collection, id) {
1731
+ async function handleContentPublish(db, collection, id, options = {}) {
1718
1732
  try {
1719
1733
  const item = await withTransaction(db, async (trx) => {
1720
1734
  const repo = new ContentRepository(trx);
1721
1735
  const resolvedId = await resolveId(repo, collection, id) ?? id;
1722
- return repo.publish(collection, resolvedId);
1736
+ return repo.publish(collection, resolvedId, options.publishedAt);
1723
1737
  });
1724
1738
  await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
1725
1739
  return {
@@ -1727,6 +1741,13 @@ async function handleContentPublish(db, collection, id) {
1727
1741
  data: { item }
1728
1742
  };
1729
1743
  } catch (error) {
1744
+ if (error instanceof EmDashValidationError) return {
1745
+ success: false,
1746
+ error: {
1747
+ code: "VALIDATION_ERROR",
1748
+ message: error.message
1749
+ }
1750
+ };
1730
1751
  console.error("Content publish error:", error);
1731
1752
  return {
1732
1753
  success: false,
@@ -1756,6 +1777,13 @@ async function handleContentUnpublish(db, collection, id) {
1756
1777
  data: { item }
1757
1778
  };
1758
1779
  } catch (error) {
1780
+ if (error instanceof EmDashValidationError) return {
1781
+ success: false,
1782
+ error: {
1783
+ code: "VALIDATION_ERROR",
1784
+ message: error.message
1785
+ }
1786
+ };
1759
1787
  console.error("Content unpublish error:", error);
1760
1788
  return {
1761
1789
  success: false,
@@ -2118,9 +2146,7 @@ async function handleRevisionGet(db, revisionId) {
2118
2146
  */
2119
2147
  async function handleRevisionRestore(db, revisionId, callerUserId) {
2120
2148
  try {
2121
- const revisionRepo = new RevisionRepository(db);
2122
- const contentRepo = new ContentRepository(db);
2123
- const revision = await revisionRepo.findById(revisionId);
2149
+ const revision = await new RevisionRepository(db).findById(revisionId);
2124
2150
  if (!revision) return {
2125
2151
  success: false,
2126
2152
  error: {
@@ -2129,17 +2155,22 @@ async function handleRevisionRestore(db, revisionId, callerUserId) {
2129
2155
  }
2130
2156
  };
2131
2157
  const { _slug, ...fieldData } = revision.data;
2132
- const item = await contentRepo.update(revision.collection, revision.entryId, {
2133
- data: fieldData,
2134
- slug: typeof _slug === "string" ? _slug : void 0
2135
- });
2136
- await revisionRepo.create({
2137
- collection: revision.collection,
2138
- entryId: revision.entryId,
2139
- data: revision.data,
2140
- authorId: callerUserId
2158
+ const item = await withTransaction(db, async (trx) => {
2159
+ const trxContentRepo = new ContentRepository(trx);
2160
+ const trxRevisionRepo = new RevisionRepository(trx);
2161
+ const updated = await trxContentRepo.update(revision.collection, revision.entryId, {
2162
+ data: fieldData,
2163
+ slug: typeof _slug === "string" ? _slug : void 0
2164
+ });
2165
+ await trxRevisionRepo.create({
2166
+ collection: revision.collection,
2167
+ entryId: revision.entryId,
2168
+ data: revision.data,
2169
+ authorId: callerUserId
2170
+ });
2171
+ return updated;
2141
2172
  });
2142
- revisionRepo.pruneOldRevisions(revision.collection, revision.entryId, 50).catch(() => {});
2173
+ new RevisionRepository(db).pruneOldRevisions(revision.collection, revision.entryId, 50).catch(() => {});
2143
2174
  return {
2144
2175
  success: true,
2145
2176
  data: { item }
@@ -2633,6 +2664,8 @@ const HTTP_SCHEME_RE = /^https?:\/\//i;
2633
2664
  const httpUrl = z$1.string().url().refine((url) => HTTP_SCHEME_RE.test(url), "URL must use http or https");
2634
2665
  /** BCP 47 locale code — language with optional script/region subtags (e.g. en, en-US, pt-BR, es-419, zh-Hant) */
2635
2666
  const localeCode = z$1.string().regex(/^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i, "Invalid locale code").transform((v) => v.toLowerCase());
2667
+ /** Shared `?locale=xx` query shape for endpoints that filter by locale. */
2668
+ const localeFilterQuery = z$1.object({ locale: z$1.string().min(1).optional() }).meta({ id: "LocaleFilterQuery" });
2636
2669
  /** Standard API error response */
2637
2670
  const apiErrorSchema = z$1.object({ error: z$1.object({
2638
2671
  code: z$1.string().meta({
@@ -2747,6 +2780,10 @@ const contentScheduleBody = z$1.object({ scheduledAt: z$1.string().min(1, "sched
2747
2780
  description: "ISO 8601 datetime for scheduled publishing",
2748
2781
  example: "2025-06-15T09:00:00Z"
2749
2782
  }) }).meta({ id: "ContentScheduleBody" });
2783
+ const contentPublishBody = z$1.object({ publishedAt: z$1.iso.datetime({
2784
+ offset: true,
2785
+ message: "must be an ISO 8601 datetime"
2786
+ }).optional().meta({ description: "Optional ISO 8601 datetime to backdate the publish (e.g. when migrating content). Requires content:publish_any permission. Without this, existing published_at is preserved on re-publish." }) }).meta({ id: "ContentPublishBody" });
2750
2787
  const contentPreviewUrlBody = z$1.object({
2751
2788
  expiresIn: z$1.union([z$1.string(), z$1.number()]).optional(),
2752
2789
  pathPattern: z$1.string().optional()
@@ -3088,8 +3125,8 @@ const commentListQuery = z$1.object({
3088
3125
  ]).optional(),
3089
3126
  collection: z$1.string().optional(),
3090
3127
  search: z$1.string().optional(),
3091
- limit: z$1.coerce.number().int().min(1).max(100).optional(),
3092
- cursor: z$1.string().optional()
3128
+ limit: z$1.coerce.number().int().min(1).max(100).optional().default(50),
3129
+ cursor: z$1.string().max(2048).optional()
3093
3130
  }).meta({ id: "CommentListQuery" });
3094
3131
  const commentStatusValues = z$1.enum([
3095
3132
  "pending",
@@ -3262,7 +3299,9 @@ const menuItemType = z$1.string().min(1);
3262
3299
  const safeHref = z$1.string().trim().refine(isSafeHref, "URL must use http, https, mailto, tel, a relative path, or a fragment identifier");
3263
3300
  const createMenuBody = z$1.object({
3264
3301
  name: z$1.string().min(1),
3265
- label: z$1.string().min(1)
3302
+ label: z$1.string().min(1),
3303
+ locale: z$1.string().min(1).optional(),
3304
+ translationOf: z$1.string().min(1).optional()
3266
3305
  }).meta({ id: "CreateMenuBody" });
3267
3306
  const updateMenuBody = z$1.object({ label: z$1.string().min(1).optional() }).meta({ id: "UpdateMenuBody" });
3268
3307
  const createMenuItemBody = z$1.object({
@@ -3298,7 +3337,9 @@ const menuSchema = z$1.object({
3298
3337
  name: z$1.string(),
3299
3338
  label: z$1.string(),
3300
3339
  created_at: z$1.string(),
3301
- updated_at: z$1.string()
3340
+ updated_at: z$1.string(),
3341
+ locale: z$1.string(),
3342
+ translation_group: z$1.string().nullable()
3302
3343
  }).meta({ id: "Menu" });
3303
3344
  const menuItemSchema = z$1.object({
3304
3345
  id: z$1.string(),
@@ -3313,8 +3354,20 @@ const menuItemSchema = z$1.object({
3313
3354
  title_attr: z$1.string().nullable(),
3314
3355
  target: z$1.string().nullable(),
3315
3356
  css_classes: z$1.string().nullable(),
3316
- created_at: z$1.string()
3357
+ created_at: z$1.string(),
3358
+ locale: z$1.string(),
3359
+ translation_group: z$1.string().nullable()
3317
3360
  }).meta({ id: "MenuItem" });
3361
+ const menuTranslationsSchema = z$1.object({
3362
+ translationGroup: z$1.string().nullable(),
3363
+ translations: z$1.array(z$1.object({
3364
+ id: z$1.string(),
3365
+ name: z$1.string(),
3366
+ label: z$1.string(),
3367
+ locale: z$1.string(),
3368
+ updatedAt: z$1.string()
3369
+ }))
3370
+ }).meta({ id: "MenuTranslations" });
3318
3371
  const menuListItemSchema = menuSchema.extend({ itemCount: z$1.number().int() }).meta({ id: "MenuListItem" });
3319
3372
  const menuWithItemsSchema = menuSchema.extend({ items: z$1.array(menuItemSchema) }).meta({ id: "MenuWithItems" });
3320
3373
 
@@ -3325,14 +3378,19 @@ const collectionSlugPattern = /^[a-z][a-z0-9_]*$/;
3325
3378
  const createTaxonomyDefBody = z$1.object({
3326
3379
  name: z$1.string().min(1).max(63).regex(/^[a-z][a-z0-9_]*$/, "Name must be lowercase alphanumeric with underscores"),
3327
3380
  label: z$1.string().min(1).max(200),
3381
+ labelSingular: z$1.string().min(1).max(200).optional(),
3328
3382
  hierarchical: z$1.boolean().optional().default(false),
3329
- collections: z$1.array(z$1.string().min(1).max(63).regex(collectionSlugPattern, "Invalid collection slug format")).max(100).optional().default([])
3383
+ collections: z$1.array(z$1.string().min(1).max(63).regex(collectionSlugPattern, "Invalid collection slug format")).max(100).optional().default([]),
3384
+ locale: z$1.string().min(1).optional(),
3385
+ translationOf: z$1.string().min(1).optional()
3330
3386
  }).meta({ id: "CreateTaxonomyDefBody" });
3331
3387
  const createTermBody = z$1.object({
3332
3388
  slug: z$1.string().min(1),
3333
3389
  label: z$1.string().min(1),
3334
3390
  parentId: z$1.string().nullish(),
3335
- description: z$1.string().optional()
3391
+ description: z$1.string().optional(),
3392
+ locale: z$1.string().min(1).optional(),
3393
+ translationOf: z$1.string().min(1).optional()
3336
3394
  }).meta({ id: "CreateTermBody" });
3337
3395
  const updateTermBody = z$1.object({
3338
3396
  slug: z$1.string().min(1).optional(),
@@ -3346,8 +3404,19 @@ const taxonomyDefSchema = z$1.object({
3346
3404
  label: z$1.string(),
3347
3405
  labelSingular: z$1.string().optional(),
3348
3406
  hierarchical: z$1.boolean(),
3349
- collections: z$1.array(z$1.string())
3407
+ collections: z$1.array(z$1.string()),
3408
+ locale: z$1.string(),
3409
+ translationGroup: z$1.string().nullable()
3350
3410
  }).meta({ id: "TaxonomyDef" });
3411
+ const taxonomyDefTranslationsSchema = z$1.object({
3412
+ translationGroup: z$1.string().nullable(),
3413
+ translations: z$1.array(z$1.object({
3414
+ id: z$1.string(),
3415
+ name: z$1.string(),
3416
+ label: z$1.string(),
3417
+ locale: z$1.string()
3418
+ }))
3419
+ }).meta({ id: "TaxonomyDefTranslations" });
3351
3420
  const taxonomyListResponseSchema = z$1.object({ taxonomies: z$1.array(taxonomyDefSchema) }).meta({ id: "TaxonomyListResponse" });
3352
3421
  const termSchema = z$1.object({
3353
3422
  id: z$1.string(),
@@ -3355,8 +3424,19 @@ const termSchema = z$1.object({
3355
3424
  slug: z$1.string(),
3356
3425
  label: z$1.string(),
3357
3426
  parentId: z$1.string().nullable(),
3358
- description: z$1.string().optional()
3427
+ description: z$1.string().optional(),
3428
+ locale: z$1.string(),
3429
+ translationGroup: z$1.string().nullable()
3359
3430
  }).meta({ id: "Term" });
3431
+ const termTranslationsSchema = z$1.object({
3432
+ translationGroup: z$1.string().nullable(),
3433
+ translations: z$1.array(z$1.object({
3434
+ id: z$1.string(),
3435
+ slug: z$1.string(),
3436
+ label: z$1.string(),
3437
+ locale: z$1.string()
3438
+ }))
3439
+ }).meta({ id: "TermTranslations" });
3360
3440
  const termWithCountSchema = z$1.object({
3361
3441
  id: z$1.string(),
3362
3442
  name: z$1.string(),
@@ -3365,7 +3445,9 @@ const termWithCountSchema = z$1.object({
3365
3445
  parentId: z$1.string().nullable(),
3366
3446
  description: z$1.string().optional(),
3367
3447
  count: z$1.number().int(),
3368
- children: z$1.array(z$1.lazy(() => termWithCountSchema))
3448
+ children: z$1.array(z$1.lazy(() => termWithCountSchema)),
3449
+ locale: z$1.string(),
3450
+ translationGroup: z$1.string().nullable()
3369
3451
  }).meta({ id: "TermWithCount" });
3370
3452
  const termListResponseSchema = z$1.object({ terms: z$1.array(termWithCountSchema) }).meta({ id: "TermListResponse" });
3371
3453
  const termResponseSchema = z$1.object({ term: termSchema }).meta({ id: "TermResponse" });
@@ -3388,8 +3470,8 @@ const sectionSource = z$1.enum([
3388
3470
  const sectionsListQuery = z$1.object({
3389
3471
  source: sectionSource.optional(),
3390
3472
  search: z$1.string().optional(),
3391
- limit: z$1.coerce.number().int().min(1).max(100).optional(),
3392
- cursor: z$1.string().optional()
3473
+ limit: z$1.coerce.number().int().min(1).max(100).optional().default(50),
3474
+ cursor: z$1.string().max(2048).optional()
3393
3475
  }).meta({ id: "SectionsListQuery" });
3394
3476
  const createSectionBody = z$1.object({
3395
3477
  slug: z$1.string().min(1),
@@ -3398,7 +3480,7 @@ const createSectionBody = z$1.object({
3398
3480
  keywords: z$1.array(z$1.string()).optional(),
3399
3481
  content: z$1.array(z$1.record(z$1.string(), z$1.unknown())),
3400
3482
  previewMediaId: z$1.string().optional(),
3401
- source: sectionSource.optional(),
3483
+ source: z$1.enum(["user", "import"]).optional(),
3402
3484
  themeId: z$1.string().optional()
3403
3485
  }).meta({ id: "CreateSectionBody" });
3404
3486
  const updateSectionBody = z$1.object({
@@ -3581,7 +3663,7 @@ const setupAtprotoAdminBody = z$1.object({ handle: z$1.string().trim().min(1) })
3581
3663
  const usersListQuery = z$1.object({
3582
3664
  search: z$1.string().optional(),
3583
3665
  role: z$1.string().optional(),
3584
- cursor: z$1.string().optional(),
3666
+ cursor: z$1.string().max(2048).optional(),
3585
3667
  limit: z$1.coerce.number().int().min(1).max(100).optional().default(50)
3586
3668
  }).meta({ id: "UsersListQuery" });
3587
3669
  const userUpdateBody = z$1.object({
@@ -4971,6 +5053,20 @@ function buildNavMenus(navMenuItemPosts, menuTermsBySlug) {
4971
5053
 
4972
5054
  //#endregion
4973
5055
  //#region src/plugins/define-plugin.ts
5056
+ /**
5057
+ * definePlugin() Helper
5058
+ *
5059
+ * Creates a properly typed and normalized plugin definition.
5060
+ * Supports two formats:
5061
+ *
5062
+ * 1. **Native format** -- full PluginDefinition with id, version, capabilities, etc.
5063
+ * Returns a ResolvedPlugin.
5064
+ *
5065
+ * 2. **Standard format** -- just { hooks, routes }. No id/version/capabilities.
5066
+ * Returns the same object (identity function for type inference).
5067
+ * Metadata comes from the descriptor at config time.
5068
+ *
5069
+ */
4974
5070
  const SIMPLE_ID = /^[a-z0-9-]+$/;
4975
5071
  const SCOPED_ID = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
4976
5072
  const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
@@ -4990,6 +5086,17 @@ function defineNativePlugin(definition) {
4990
5086
  if (!SIMPLE_ID.test(id) && !SCOPED_ID.test(id)) throw new Error(`Invalid plugin id "${id}". Must be lowercase alphanumeric with dashes (e.g., "my-plugin" or "@scope/my-plugin").`);
4991
5087
  if (!SEMVER_PATTERN.test(version)) throw new Error(`Invalid plugin version "${version}". Must be semver format (e.g., "1.0.0").`);
4992
5088
  const validCapabilities = new Set([
5089
+ "network:request",
5090
+ "network:request:unrestricted",
5091
+ "content:read",
5092
+ "content:write",
5093
+ "media:read",
5094
+ "media:write",
5095
+ "users:read",
5096
+ "email:send",
5097
+ "hooks.email-transport:register",
5098
+ "hooks.email-events:register",
5099
+ "hooks.page-fragments:register",
4993
5100
  "network:fetch",
4994
5101
  "network:fetch:any",
4995
5102
  "read:content",
@@ -4997,16 +5104,16 @@ function defineNativePlugin(definition) {
4997
5104
  "read:media",
4998
5105
  "write:media",
4999
5106
  "read:users",
5000
- "email:send",
5001
5107
  "email:provide",
5002
5108
  "email:intercept",
5003
5109
  "page:inject"
5004
5110
  ]);
5005
5111
  for (const cap of capabilities) if (!validCapabilities.has(cap)) throw new Error(`Invalid capability "${cap}" in plugin "${id}".`);
5006
- const normalizedCapabilities = [...capabilities];
5007
- if (capabilities.includes("write:content") && !capabilities.includes("read:content")) normalizedCapabilities.push("read:content");
5008
- if (capabilities.includes("write:media") && !capabilities.includes("read:media")) normalizedCapabilities.push("read:media");
5009
- if (capabilities.includes("network:fetch:any") && !capabilities.includes("network:fetch")) normalizedCapabilities.push("network:fetch");
5112
+ const canonical = normalizeCapabilities(capabilities);
5113
+ const normalizedCapabilities = [...canonical];
5114
+ if (canonical.includes("content:write") && !canonical.includes("content:read")) normalizedCapabilities.push("content:read");
5115
+ if (canonical.includes("media:write") && !canonical.includes("media:read")) normalizedCapabilities.push("media:read");
5116
+ if (canonical.includes("network:request:unrestricted") && !canonical.includes("network:request")) normalizedCapabilities.push("network:request");
5010
5117
  return {
5011
5118
  id,
5012
5119
  version,
@@ -6017,16 +6124,16 @@ var PluginContextFactory = class {
6017
6124
  const log = createLogAccess(plugin.id);
6018
6125
  const storage = createStorageAccess(this.db, plugin.id, plugin.storage);
6019
6126
  let content;
6020
- if (capabilities.has("write:content")) content = createContentAccessWithWrite(this.db);
6021
- else if (capabilities.has("read:content")) content = createContentAccess(this.db);
6127
+ if (capabilities.has("content:write")) content = createContentAccessWithWrite(this.db);
6128
+ else if (capabilities.has("content:read")) content = createContentAccess(this.db);
6022
6129
  let media;
6023
- if (capabilities.has("write:media") && this.getUploadUrl) media = createMediaAccessWithWrite(this.db, this.getUploadUrl, this.storage);
6024
- else if (capabilities.has("read:media")) media = createMediaAccess(this.db);
6130
+ if (capabilities.has("media:write") && this.getUploadUrl) media = createMediaAccessWithWrite(this.db, this.getUploadUrl, this.storage);
6131
+ else if (capabilities.has("media:read")) media = createMediaAccess(this.db);
6025
6132
  let http;
6026
- if (capabilities.has("network:fetch:any")) http = createUnrestrictedHttpAccess(plugin.id);
6027
- else if (capabilities.has("network:fetch")) http = createHttpAccess(plugin.id, plugin.allowedHosts);
6133
+ if (capabilities.has("network:request:unrestricted")) http = createUnrestrictedHttpAccess(plugin.id);
6134
+ else if (capabilities.has("network:request")) http = createHttpAccess(plugin.id, plugin.allowedHosts);
6028
6135
  let users;
6029
- if (capabilities.has("read:users")) users = createUserAccess(this.db);
6136
+ if (capabilities.has("users:read")) users = createUserAccess(this.db);
6030
6137
  let cron;
6031
6138
  if (this.cronReschedule) cron = new CronAccessImpl(this.db, plugin.id, this.cronReschedule);
6032
6139
  let email;
@@ -6172,22 +6279,22 @@ var HookPipeline = class HookPipeline {
6172
6279
  * capability will have that hook silently skipped at registration time.
6173
6280
  */
6174
6281
  static HOOK_REQUIRED_CAPABILITY = new Map([
6175
- ["email:beforeSend", "email:intercept"],
6176
- ["email:afterSend", "email:intercept"],
6177
- ["email:deliver", "email:provide"],
6178
- ["content:beforeSave", "write:content"],
6179
- ["content:afterSave", "read:content"],
6180
- ["content:beforeDelete", "read:content"],
6181
- ["content:afterDelete", "read:content"],
6182
- ["content:afterPublish", "read:content"],
6183
- ["content:afterUnpublish", "read:content"],
6184
- ["media:beforeUpload", "write:media"],
6185
- ["media:afterUpload", "read:media"],
6186
- ["comment:beforeCreate", "read:users"],
6187
- ["comment:moderate", "read:users"],
6188
- ["comment:afterCreate", "read:users"],
6189
- ["comment:afterModerate", "read:users"],
6190
- ["page:fragments", "page:inject"]
6282
+ ["email:beforeSend", "hooks.email-events:register"],
6283
+ ["email:afterSend", "hooks.email-events:register"],
6284
+ ["email:deliver", "hooks.email-transport:register"],
6285
+ ["content:beforeSave", "content:write"],
6286
+ ["content:afterSave", "content:read"],
6287
+ ["content:beforeDelete", "content:read"],
6288
+ ["content:afterDelete", "content:read"],
6289
+ ["content:afterPublish", "content:read"],
6290
+ ["content:afterUnpublish", "content:read"],
6291
+ ["media:beforeUpload", "media:write"],
6292
+ ["media:afterUpload", "media:read"],
6293
+ ["comment:beforeCreate", "users:read"],
6294
+ ["comment:moderate", "users:read"],
6295
+ ["comment:afterCreate", "users:read"],
6296
+ ["comment:afterModerate", "users:read"],
6297
+ ["page:fragments", "hooks.page-fragments:register"]
6191
6298
  ]);
6192
6299
  /**
6193
6300
  * Register a single plugin's hook by name
@@ -7786,18 +7893,6 @@ function createNoopSandboxRunner(_options) {
7786
7893
  return new NoopSandboxRunner();
7787
7894
  }
7788
7895
 
7789
- //#endregion
7790
- //#region src/plugins/types.ts
7791
- /**
7792
- * Check if a value is a StandardPluginDefinition (has hooks/routes but no id/version).
7793
- */
7794
- function isStandardPluginDefinition(value) {
7795
- if (typeof value !== "object" || value === null) return false;
7796
- const hasPluginShape = "hooks" in value || "routes" in value;
7797
- const hasNativeShape = "id" in value && "version" in value;
7798
- return hasPluginShape && !hasNativeShape;
7799
- }
7800
-
7801
7896
  //#endregion
7802
7897
  //#region src/import/sections.ts
7803
7898
  /**
@@ -8721,6 +8816,7 @@ registerSource(wxrSource);
8721
8816
  *
8722
8817
  * Creates preview URLs that include a signed token for accessing draft content.
8723
8818
  */
8819
+ const REPEATED_SLASHES = /\/{2,}/g;
8724
8820
  /**
8725
8821
  * Generate a preview URL for content
8726
8822
  *
@@ -8755,13 +8851,15 @@ registerSource(wxrSource);
8755
8851
  * ```
8756
8852
  */
8757
8853
  async function getPreviewUrl(options) {
8758
- const { collection, id, secret, expiresIn = "1h", baseUrl, pathPattern = "/{collection}/{id}" } = options;
8854
+ const { collection, id, secret, expiresIn = "1h", baseUrl, pathPattern = "/{collection}/{id}", locale = "" } = options;
8759
8855
  const token = await generatePreviewToken({
8760
8856
  contentId: `${collection}:${id}`,
8761
8857
  expiresIn,
8762
8858
  secret
8763
8859
  });
8764
- const path = pathPattern.replace("{collection}", collection).replace("{id}", id);
8860
+ let path = pathPattern.replace("{collection}", collection).replace("{id}", id).replace("{locale}", locale);
8861
+ path = path.replace(REPEATED_SLASHES, "/");
8862
+ if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
8765
8863
  const url = new URL(path, baseUrl || "http://placeholder");
8766
8864
  url.searchParams.set("_preview", token);
8767
8865
  if (!baseUrl) return `${url.pathname}${url.search}`;
@@ -8880,53 +8978,53 @@ async function getCommentCountWithDb(db, collection, contentId) {
8880
8978
  //#endregion
8881
8979
  //#region src/menus/index.ts
8882
8980
  /**
8883
- * Get menu by name with resolved URLs
8981
+ * Get a menu by name with resolved URLs.
8884
8982
  *
8885
8983
  * @example
8886
8984
  * ```ts
8887
- * import { getMenu } from "emdash";
8888
- *
8889
8985
  * const menu = await getMenu("primary");
8890
- * if (menu) {
8891
- * console.log(menu.items); // Array of MenuItem with resolved URLs
8892
- * }
8986
+ * const menuEs = await getMenu("primary", { locale: "es" });
8893
8987
  * ```
8894
8988
  */
8895
- function getMenu(name) {
8896
- return requestCached(`menu:${name}`, async () => {
8897
- return getMenuWithDb(name, await getDb());
8989
+ function getMenu(name, options = {}) {
8990
+ const locale = resolveLocale(options.locale);
8991
+ return requestCached(`menu:${name}:${locale ?? "*"}`, async () => {
8992
+ return getMenuWithDb(name, await getDb(), { locale });
8898
8993
  });
8899
8994
  }
8900
8995
  /**
8901
- * Get menu by name with resolved URLs (with explicit db)
8902
- *
8903
- * @internal Use `getMenu()` in templates. This variant is for admin routes
8904
- * that already have a database handle.
8996
+ * Get menu by name with resolved URLs (with explicit db). Internal helper for
8997
+ * admin routes that already have a database handle.
8905
8998
  */
8906
- async function getMenuWithDb(name, db) {
8907
- const menuRow = await db.selectFrom("_emdash_menus").selectAll().where("name", "=", name).executeTakeFirst();
8999
+ async function getMenuWithDb(name, db, options = {}) {
9000
+ const chain = resolveLocaleChain(options.locale);
9001
+ const selectMenu = () => db.selectFrom("_emdash_menus").selectAll().where("name", "=", name);
9002
+ let menuRow;
9003
+ if (chain.length === 0) menuRow = await selectMenu().orderBy("locale", "asc").executeTakeFirst();
9004
+ else {
9005
+ menuRow = void 0;
9006
+ for (const locale of chain) {
9007
+ menuRow = await selectMenu().where("locale", "=", locale).executeTakeFirst();
9008
+ if (menuRow) break;
9009
+ }
9010
+ }
8908
9011
  if (!menuRow) return null;
8909
- const items = await buildMenuTree(await db.selectFrom("_emdash_menu_items").selectAll().$castTo().where("menu_id", "=", menuRow.id).orderBy("sort_order", "asc").execute(), db);
9012
+ const items = await buildMenuTree(await db.selectFrom("_emdash_menu_items").selectAll().$castTo().where("menu_id", "=", menuRow.id).orderBy("sort_order", "asc").execute(), db, menuRow.locale);
8910
9013
  return {
8911
9014
  id: menuRow.id,
8912
9015
  name: menuRow.name,
8913
9016
  label: menuRow.label,
8914
- items
9017
+ items,
9018
+ locale: menuRow.locale,
9019
+ translationGroup: menuRow.translation_group
8915
9020
  };
8916
9021
  }
8917
9022
  /**
8918
- * Get all menus (without items - for admin list)
8919
- *
8920
- * @example
8921
- * ```ts
8922
- * import { getMenus } from "emdash";
8923
- *
8924
- * const menus = await getMenus();
8925
- * console.log(menus); // [{ id, name, label }]
8926
- * ```
9023
+ * Get all menus (without items, locale-filtered for admin list / site nav
9024
+ * summaries). When no locale is configured, returns menus across all locales.
8927
9025
  */
8928
- async function getMenus() {
8929
- return getMenusWithDb(await getDb());
9026
+ async function getMenus(options = {}) {
9027
+ return getMenusWithDb(await getDb(), options);
8930
9028
  }
8931
9029
  /**
8932
9030
  * Get all menus (with explicit db)
@@ -8934,17 +9032,23 @@ async function getMenus() {
8934
9032
  * @internal Use `getMenus()` in templates. This variant is for admin routes
8935
9033
  * that already have a database handle.
8936
9034
  */
8937
- async function getMenusWithDb(db) {
8938
- return await db.selectFrom("_emdash_menus").select([
9035
+ async function getMenusWithDb(db, options = {}) {
9036
+ const locale = resolveLocale(options.locale);
9037
+ let query = db.selectFrom("_emdash_menus").select([
8939
9038
  "id",
8940
9039
  "name",
8941
- "label"
8942
- ]).orderBy("name", "asc").execute();
9040
+ "label",
9041
+ "locale"
9042
+ ]).orderBy("name", "asc");
9043
+ if (locale !== void 0) query = query.where("locale", "=", locale);
9044
+ return query.execute();
8943
9045
  }
8944
9046
  /**
8945
- * Build hierarchical menu tree from flat array of items
9047
+ * Build a hierarchical menu tree from a flat list of items. Items are
9048
+ * resolved against the given `locale` so references land on the right
9049
+ * per-locale content rows.
8946
9050
  */
8947
- async function buildMenuTree(items, db) {
9051
+ async function buildMenuTree(items, db, locale) {
8948
9052
  const collectionSlugs = /* @__PURE__ */ new Set();
8949
9053
  for (const item of items) {
8950
9054
  if (item.reference_collection) collectionSlugs.add(item.reference_collection);
@@ -8955,7 +9059,7 @@ async function buildMenuTree(items, db) {
8955
9059
  const rows = await db.selectFrom("_emdash_collections").select(["slug", "url_pattern"]).where("slug", "in", [...collectionSlugs]).execute();
8956
9060
  for (const row of rows) urlPatterns.set(row.slug, row.url_pattern);
8957
9061
  }
8958
- const validItems = (await Promise.all(items.map((item) => resolveMenuItem(item, db, urlPatterns)))).filter((item) => item !== null);
9062
+ const validItems = (await Promise.all(items.map((item) => resolveMenuItem(item, db, urlPatterns, locale)))).filter((item) => item !== null);
8959
9063
  const itemMap = /* @__PURE__ */ new Map();
8960
9064
  const rootItems = [];
8961
9065
  for (const item of validItems) itemMap.set(item.id, {
@@ -8974,11 +9078,11 @@ async function buildMenuTree(items, db) {
8974
9078
  return rootItems;
8975
9079
  }
8976
9080
  /**
8977
- * Resolve a single menu item's URL
8978
- *
8979
- * Returns null if the referenced content no longer exists (item should be skipped)
9081
+ * Resolve a single menu item's URL. `reference_id` is a translation_group
9082
+ * (migration 036 remapped all existing references); we join it against
9083
+ * the per-locale ec_* row or per-locale taxonomy row.
8980
9084
  */
8981
- async function resolveMenuItem(item, db, urlPatterns) {
9085
+ async function resolveMenuItem(item, db, urlPatterns, locale) {
8982
9086
  let url;
8983
9087
  try {
8984
9088
  switch (item.type) {
@@ -8987,18 +9091,18 @@ async function resolveMenuItem(item, db, urlPatterns) {
8987
9091
  break;
8988
9092
  case "page":
8989
9093
  case "post":
8990
- url = await resolveContentUrl(item.reference_collection || `${item.type}s`, item.reference_id, db, urlPatterns);
9094
+ url = await resolveContentUrl(item.reference_collection || `${item.type}s`, item.reference_id, db, urlPatterns, locale);
8991
9095
  if (url === null) return null;
8992
9096
  break;
8993
9097
  case "taxonomy":
8994
- url = await resolveTaxonomyUrl(item.reference_id, db);
9098
+ url = await resolveTaxonomyUrl(item.reference_id, db, locale);
8995
9099
  if (url === null) return null;
8996
9100
  break;
8997
9101
  case "collection":
8998
9102
  url = `/${item.reference_collection}/`;
8999
9103
  break;
9000
9104
  default: if (item.reference_collection && item.reference_id) {
9001
- url = await resolveContentUrl(item.reference_collection, item.reference_id, db, urlPatterns);
9105
+ url = await resolveContentUrl(item.reference_collection, item.reference_id, db, urlPatterns, locale);
9002
9106
  if (url === null) return null;
9003
9107
  } else url = "#";
9004
9108
  }
@@ -9027,37 +9131,51 @@ function interpolateUrlPattern(pattern, slug, id) {
9027
9131
  return pattern.replace(SLUG_PLACEHOLDER, slug).replace(ID_PLACEHOLDER, id);
9028
9132
  }
9029
9133
  /**
9030
- * Resolve URL for a content entry (page/post)
9031
- *
9032
- * Uses the collection's url_pattern if set, otherwise falls back to /{collection}/{slug}.
9033
- * Returns null if content not found (item should be skipped).
9134
+ * Resolve the URL for a content reference. `referenceGroup` is the content
9135
+ * row's translation_group; we look up the row in the requested locale
9136
+ * (falling back to the source if no translation exists so the menu link is
9137
+ * still clickable).
9034
9138
  */
9035
- async function resolveContentUrl(collection, entryId, db, urlPatterns) {
9036
- if (!entryId) return null;
9139
+ async function resolveContentUrl(collection, referenceGroup, db, urlPatterns, locale) {
9140
+ if (!referenceGroup) return null;
9037
9141
  try {
9038
9142
  validateIdentifier(collection, "menu item collection");
9039
- const row = (await sql`
9040
- SELECT slug FROM ${sql.ref(`ec_${collection}`)} WHERE id = ${entryId} LIMIT 1
9041
- `.execute(db)).rows[0];
9042
- if (row) {
9043
- const pattern = urlPatterns.get(collection);
9044
- if (pattern) return interpolateUrlPattern(pattern, row.slug, entryId);
9045
- return `/${collection}/${row.slug}`;
9143
+ let result = await sql`
9144
+ SELECT id, slug FROM ${sql.ref(`ec_${collection}`)}
9145
+ WHERE translation_group = ${referenceGroup} AND locale = ${locale}
9146
+ LIMIT 1
9147
+ `.execute(db);
9148
+ let row = result.rows[0];
9149
+ if (!row) {
9150
+ result = await sql`
9151
+ SELECT id, slug FROM ${sql.ref(`ec_${collection}`)}
9152
+ WHERE translation_group = ${referenceGroup}
9153
+ ORDER BY locale ASC LIMIT 1
9154
+ `.execute(db);
9155
+ row = result.rows[0];
9046
9156
  }
9047
- return null;
9157
+ if (!row) row = (await sql`
9158
+ SELECT id, slug FROM ${sql.ref(`ec_${collection}`)}
9159
+ WHERE id = ${referenceGroup} LIMIT 1
9160
+ `.execute(db)).rows[0];
9161
+ if (!row) return null;
9162
+ const pattern = urlPatterns.get(collection);
9163
+ if (pattern) return interpolateUrlPattern(pattern, row.slug, row.id);
9164
+ return `/${collection}/${row.slug}`;
9048
9165
  } catch (error) {
9049
- console.error(`Failed to resolve content URL for ${collection}/${entryId}:`, error);
9166
+ console.error(`Failed to resolve content URL for ${collection}/${referenceGroup}:`, error);
9050
9167
  return null;
9051
9168
  }
9052
9169
  }
9053
9170
  /**
9054
- * Resolve URL for a taxonomy term
9055
- *
9056
- * Returns null if taxonomy not found (item should be skipped)
9171
+ * Resolve URL for a taxonomy term reference. `referenceGroup` is the term's
9172
+ * translation_group; we pick the row in the active locale (or fall back).
9057
9173
  */
9058
- async function resolveTaxonomyUrl(taxonomyId, db) {
9059
- if (!taxonomyId) return null;
9060
- const taxonomy = await db.selectFrom("taxonomies").select(["name", "slug"]).where("id", "=", taxonomyId).executeTakeFirst();
9174
+ async function resolveTaxonomyUrl(referenceGroup, db, locale) {
9175
+ if (!referenceGroup) return null;
9176
+ let taxonomy = await db.selectFrom("taxonomies").select(["name", "slug"]).where("translation_group", "=", referenceGroup).where("locale", "=", locale).executeTakeFirst();
9177
+ if (!taxonomy) taxonomy = await db.selectFrom("taxonomies").select(["name", "slug"]).where("translation_group", "=", referenceGroup).orderBy("locale", "asc").executeTakeFirst();
9178
+ if (!taxonomy) taxonomy = await db.selectFrom("taxonomies").select(["name", "slug"]).where("id", "=", referenceGroup).executeTakeFirst();
9061
9179
  if (!taxonomy) return null;
9062
9180
  return `/${taxonomy.name}/${taxonomy.slug}`;
9063
9181
  }
@@ -9633,5 +9751,5 @@ function extractSearchableFields(entry, fields) {
9633
9751
  }
9634
9752
 
9635
9753
  //#endregion
9636
- export { prosemirrorToPortableText as $, isStandardPluginDefinition as A, handleContentSchedule as At, EmailPipeline as B, getAllSources as C, handleContentGet as Ct, probeUrl as D, handleContentPermanentDelete as Dt, getUrlSources as E, handleContentListTrashed as Et, createPluginManager as F, validateRev as Ft, extractRequestMeta as G, createHookPipeline as H, PluginRouteError as I, portableText as It, definePlugin as J, sanitizeHeadersForSandbox as K, PluginRouteRegistry as L, reference as Lt, SandboxNotAvailableError as M, handleContentUnpublish as Mt, createNoopSandboxRunner as N, handleContentUnschedule as Nt, registerSource as O, handleContentPublish as Ot, PluginManager as P, handleContentUpdate as Pt, portableTextToProsemirror as Q, DEV_CONSOLE_EMAIL_PLUGIN_ID as R, image as Rt, clearSources as S, handleContentDuplicate as St, getSource as T, handleContentList as Tt, resolveExclusiveHooks as U, HookPipeline as V, CronExecutor as W, parseWxrString as X, parseWxr as Y, after as Z, buildPreviewUrl as _, handleContentCountScheduled as _t, search as a, PluginStateRepository as at, parseWxrDate as b, handleContentDelete as bt, getWidgetArea as c, handleMediaDelete as ct, getMenu as d, handleMediaUpdate as dt, isSafeHref as et, getMenus as f, handleRevisionGet as ft, isPreviewRequest as g, handleContentCompare as gt, getPreviewToken as h, generateManifest as ht, getSuggestions as i, getSections as it, NoopSandboxRunner as j, handleContentTranslations as jt, importReusableBlocksAsSections as k, handleContentRestore as kt, getWidgetAreas as l, handleMediaGet as lt, getComments as m, handleRevisionRestore as mt, extractSearchableFields as n, loadBundleFromR2 as nt, searchCollection as o, getCollectionInfo as ot, getCommentCount as p, handleRevisionList as pt, getTrustedProxyHeaders as q, getSearchStats as r, getSection as rt, searchWithDb as s, handleMediaCreate as st, extractPlainText as t, sanitizeHref as tt, getWidgetComponents as u, handleMediaList as ut, getPreviewUrl as v, handleContentCountTrashed as vt, getFileSources as w, handleContentGetIncludingTrashed as wt, wxrSource as x, handleContentDiscardDraft as xt, wordpressRestSource as y, handleContentCreate as yt, devConsoleEmailDeliver as z };
9637
- //# sourceMappingURL=search-BoZYFuUk.mjs.map
9754
+ export { isSafeHref as $, NoopSandboxRunner as A, handleContentTranslations as At, HookPipeline as B, getAllSources as C, handleContentGetIncludingTrashed as Ct, probeUrl as D, handleContentPublish as Dt, getUrlSources as E, handleContentPermanentDelete as Et, PluginRouteError as F, portableText as Ft, sanitizeHeadersForSandbox as G, resolveExclusiveHooks as H, PluginRouteRegistry as I, reference as It, parseWxr as J, getTrustedProxyHeaders as K, DEV_CONSOLE_EMAIL_PLUGIN_ID as L, image as Lt, createNoopSandboxRunner as M, handleContentUnschedule as Mt, PluginManager as N, handleContentUpdate as Nt, registerSource as O, handleContentRestore as Ot, createPluginManager as P, validateRev as Pt, prosemirrorToPortableText as Q, devConsoleEmailDeliver as R, clearSources as S, handleContentGet as St, getSource as T, handleContentListTrashed as Tt, CronExecutor as U, createHookPipeline as V, extractRequestMeta as W, after as X, parseWxrString as Y, portableTextToProsemirror as Z, buildPreviewUrl as _, handleContentCountTrashed as _t, search as a, getCollectionInfo as at, parseWxrDate as b, handleContentDiscardDraft as bt, getWidgetArea as c, handleMediaGet as ct, getMenu as d, handleRevisionGet as dt, sanitizeHref as et, getMenus as f, handleRevisionList as ft, isPreviewRequest as g, handleContentCountScheduled as gt, getPreviewToken as h, handleContentCompare as ht, getSuggestions as i, PluginStateRepository as it, SandboxNotAvailableError as j, handleContentUnpublish as jt, importReusableBlocksAsSections as k, handleContentSchedule as kt, getWidgetAreas as l, handleMediaList as lt, getComments as m, generateManifest as mt, extractSearchableFields as n, getSection as nt, searchCollection as o, handleMediaCreate as ot, getCommentCount as p, handleRevisionRestore as pt, definePlugin as q, getSearchStats as r, getSections as rt, searchWithDb as s, handleMediaDelete as st, extractPlainText as t, loadBundleFromR2 as tt, getWidgetComponents as u, handleMediaUpdate as ut, getPreviewUrl as v, handleContentCreate as vt, getFileSources as w, handleContentList as wt, wxrSource as x, handleContentDuplicate as xt, wordpressRestSource as y, handleContentDelete as yt, EmailPipeline as z };
9755
+ //# sourceMappingURL=search-DkN-BqsS.mjs.map