emdash 0.0.0-b → 0.0.2

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 (661) hide show
  1. package/README.md +87 -43
  2. package/dist/adapters-BLMa4JGD.d.mts +106 -0
  3. package/dist/adapters-BLMa4JGD.d.mts.map +1 -0
  4. package/dist/apply-Bjfq_b4-.mjs +1293 -0
  5. package/dist/apply-Bjfq_b4-.mjs.map +1 -0
  6. package/dist/astro/index.d.mts +51 -0
  7. package/dist/astro/index.d.mts.map +1 -0
  8. package/dist/astro/index.mjs +1336 -0
  9. package/dist/astro/index.mjs.map +1 -0
  10. package/dist/astro/middleware/auth.d.mts +31 -0
  11. package/dist/astro/middleware/auth.d.mts.map +1 -0
  12. package/dist/astro/middleware/auth.mjs +654 -0
  13. package/dist/astro/middleware/auth.mjs.map +1 -0
  14. package/dist/astro/middleware/redirect.d.mts +22 -0
  15. package/dist/astro/middleware/redirect.d.mts.map +1 -0
  16. package/dist/astro/middleware/redirect.mjs +63 -0
  17. package/dist/astro/middleware/redirect.mjs.map +1 -0
  18. package/dist/astro/middleware/request-context.d.mts +18 -0
  19. package/dist/astro/middleware/request-context.d.mts.map +1 -0
  20. package/dist/astro/middleware/request-context.mjs +1310 -0
  21. package/dist/astro/middleware/request-context.mjs.map +1 -0
  22. package/dist/astro/middleware/setup.d.mts +20 -0
  23. package/dist/astro/middleware/setup.d.mts.map +1 -0
  24. package/dist/astro/middleware/setup.mjs +47 -0
  25. package/dist/astro/middleware/setup.mjs.map +1 -0
  26. package/dist/astro/middleware.d.mts +13 -0
  27. package/dist/astro/middleware.d.mts.map +1 -0
  28. package/dist/astro/middleware.mjs +1613 -0
  29. package/dist/astro/middleware.mjs.map +1 -0
  30. package/dist/astro/types.d.mts +250 -0
  31. package/dist/astro/types.d.mts.map +1 -0
  32. package/dist/astro/types.mjs +1 -0
  33. package/dist/base64-MBPo9ozB.mjs +59 -0
  34. package/dist/base64-MBPo9ozB.mjs.map +1 -0
  35. package/dist/byline-CL847F26.mjs +213 -0
  36. package/dist/byline-CL847F26.mjs.map +1 -0
  37. package/dist/bylines-C2a-2TGt.mjs +136 -0
  38. package/dist/bylines-C2a-2TGt.mjs.map +1 -0
  39. package/dist/chunk-ClPoSABd.mjs +21 -0
  40. package/dist/cli/index.d.mts +1 -0
  41. package/dist/cli/index.mjs +3909 -0
  42. package/dist/cli/index.mjs.map +1 -0
  43. package/dist/client/cf-access.d.mts +60 -0
  44. package/dist/client/cf-access.d.mts.map +1 -0
  45. package/dist/client/cf-access.mjs +179 -0
  46. package/dist/client/cf-access.mjs.map +1 -0
  47. package/dist/client/index.d.mts +398 -0
  48. package/dist/client/index.d.mts.map +1 -0
  49. package/dist/client/index.mjs +346 -0
  50. package/dist/client/index.mjs.map +1 -0
  51. package/dist/config-CKE8p9xM.mjs +55 -0
  52. package/dist/config-CKE8p9xM.mjs.map +1 -0
  53. package/dist/connection-B4zVnQIa.mjs +40 -0
  54. package/dist/connection-B4zVnQIa.mjs.map +1 -0
  55. package/dist/content-D6C2WsZC.mjs +824 -0
  56. package/dist/content-D6C2WsZC.mjs.map +1 -0
  57. package/dist/db/index.d.mts +4 -0
  58. package/dist/db/index.mjs +62 -0
  59. package/dist/db/index.mjs.map +1 -0
  60. package/dist/db/libsql.d.mts +11 -0
  61. package/dist/db/libsql.d.mts.map +1 -0
  62. package/dist/db/libsql.mjs +17 -0
  63. package/dist/db/libsql.mjs.map +1 -0
  64. package/dist/db/postgres.d.mts +11 -0
  65. package/dist/db/postgres.d.mts.map +1 -0
  66. package/dist/db/postgres.mjs +30 -0
  67. package/dist/db/postgres.mjs.map +1 -0
  68. package/dist/db/sqlite.d.mts +11 -0
  69. package/dist/db/sqlite.d.mts.map +1 -0
  70. package/dist/db/sqlite.mjs +16 -0
  71. package/dist/db/sqlite.mjs.map +1 -0
  72. package/dist/default-Cyi4aAxu.mjs +81 -0
  73. package/dist/default-Cyi4aAxu.mjs.map +1 -0
  74. package/dist/dialect-helpers-B9uSp2GJ.mjs +90 -0
  75. package/dist/dialect-helpers-B9uSp2GJ.mjs.map +1 -0
  76. package/dist/error-Cxz0tQeO.mjs +27 -0
  77. package/dist/error-Cxz0tQeO.mjs.map +1 -0
  78. package/dist/index-C1xF3OGh.d.mts +4527 -0
  79. package/dist/index-C1xF3OGh.d.mts.map +1 -0
  80. package/dist/index.d.mts +16 -0
  81. package/dist/index.mjs +30 -0
  82. package/dist/load-yOOlckBj.mjs +28 -0
  83. package/dist/load-yOOlckBj.mjs.map +1 -0
  84. package/dist/loader-fz8Q_3EO.mjs +447 -0
  85. package/dist/loader-fz8Q_3EO.mjs.map +1 -0
  86. package/dist/manifest-schema-Dcl0R6nM.mjs +184 -0
  87. package/dist/manifest-schema-Dcl0R6nM.mjs.map +1 -0
  88. package/dist/media/index.d.mts +26 -0
  89. package/dist/media/index.d.mts.map +1 -0
  90. package/dist/media/index.mjs +55 -0
  91. package/dist/media/index.mjs.map +1 -0
  92. package/dist/media/local-runtime.d.mts +39 -0
  93. package/dist/media/local-runtime.d.mts.map +1 -0
  94. package/dist/media/local-runtime.mjs +133 -0
  95. package/dist/media/local-runtime.mjs.map +1 -0
  96. package/dist/media-DqHVh136.mjs +200 -0
  97. package/dist/media-DqHVh136.mjs.map +1 -0
  98. package/dist/mode-C2EzN1uE.mjs +23 -0
  99. package/dist/mode-C2EzN1uE.mjs.map +1 -0
  100. package/dist/page/index.d.mts +140 -0
  101. package/dist/page/index.d.mts.map +1 -0
  102. package/dist/page/index.mjs +416 -0
  103. package/dist/page/index.mjs.map +1 -0
  104. package/dist/placeholder-CmGAmqeO.d.mts +276 -0
  105. package/dist/placeholder-CmGAmqeO.d.mts.map +1 -0
  106. package/dist/placeholder-SmpOx-_v.mjs +243 -0
  107. package/dist/placeholder-SmpOx-_v.mjs.map +1 -0
  108. package/dist/plugin-utils.d.mts +58 -0
  109. package/dist/plugin-utils.d.mts.map +1 -0
  110. package/dist/plugin-utils.mjs +78 -0
  111. package/dist/plugin-utils.mjs.map +1 -0
  112. package/dist/plugins/adapt-sandbox-entry.d.mts +22 -0
  113. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -0
  114. package/dist/plugins/adapt-sandbox-entry.mjs +113 -0
  115. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -0
  116. package/dist/query-CS_iSj34.mjs +460 -0
  117. package/dist/query-CS_iSj34.mjs.map +1 -0
  118. package/dist/redirect-DIfIni3r.mjs +329 -0
  119. package/dist/redirect-DIfIni3r.mjs.map +1 -0
  120. package/dist/registry-D_w5HW4G.mjs +863 -0
  121. package/dist/registry-D_w5HW4G.mjs.map +1 -0
  122. package/dist/request-context.d.mts +49 -0
  123. package/dist/request-context.d.mts.map +1 -0
  124. package/dist/request-context.mjs +43 -0
  125. package/dist/request-context.mjs.map +1 -0
  126. package/dist/runner-C0hCbYnD.mjs +1412 -0
  127. package/dist/runner-C0hCbYnD.mjs.map +1 -0
  128. package/dist/runner-EAtf0ZIe.d.mts +27 -0
  129. package/dist/runner-EAtf0ZIe.d.mts.map +1 -0
  130. package/dist/runtime.d.mts +26 -0
  131. package/dist/runtime.d.mts.map +1 -0
  132. package/dist/runtime.mjs +42 -0
  133. package/dist/runtime.mjs.map +1 -0
  134. package/dist/search-DG603UrT.mjs +9211 -0
  135. package/dist/search-DG603UrT.mjs.map +1 -0
  136. package/dist/seed/index.d.mts +3 -0
  137. package/dist/seed/index.mjs +15 -0
  138. package/dist/seo/index.d.mts +70 -0
  139. package/dist/seo/index.d.mts.map +1 -0
  140. package/dist/seo/index.mjs +70 -0
  141. package/dist/seo/index.mjs.map +1 -0
  142. package/dist/storage/local.d.mts +39 -0
  143. package/dist/storage/local.d.mts.map +1 -0
  144. package/dist/storage/local.mjs +166 -0
  145. package/dist/storage/local.mjs.map +1 -0
  146. package/dist/storage/s3.d.mts +32 -0
  147. package/dist/storage/s3.d.mts.map +1 -0
  148. package/dist/storage/s3.mjs +175 -0
  149. package/dist/storage/s3.mjs.map +1 -0
  150. package/dist/tokens-DpgrkrXK.mjs +171 -0
  151. package/dist/tokens-DpgrkrXK.mjs.map +1 -0
  152. package/dist/transport-BFGblqwG.d.mts +42 -0
  153. package/dist/transport-BFGblqwG.d.mts.map +1 -0
  154. package/dist/transport-yxiQsi8I.mjs +418 -0
  155. package/dist/transport-yxiQsi8I.mjs.map +1 -0
  156. package/dist/types-BRuPJGdV.d.mts +102 -0
  157. package/dist/types-BRuPJGdV.d.mts.map +1 -0
  158. package/dist/types-C4-fAxN3.d.mts +182 -0
  159. package/dist/types-C4-fAxN3.d.mts.map +1 -0
  160. package/dist/types-CMMN0pNg.mjs +31 -0
  161. package/dist/types-CMMN0pNg.mjs.map +1 -0
  162. package/dist/types-CUBbjgmP.mjs +16 -0
  163. package/dist/types-CUBbjgmP.mjs.map +1 -0
  164. package/dist/types-DRjfYOEv.d.mts +426 -0
  165. package/dist/types-DRjfYOEv.d.mts.map +1 -0
  166. package/dist/types-DY5zk5HN.mjs +73 -0
  167. package/dist/types-DY5zk5HN.mjs.map +1 -0
  168. package/dist/types-DaNLHo_T.d.mts +184 -0
  169. package/dist/types-DaNLHo_T.d.mts.map +1 -0
  170. package/dist/types-DvhsUmSJ.d.mts +1111 -0
  171. package/dist/types-DvhsUmSJ.d.mts.map +1 -0
  172. package/dist/validate-CpBtVMsD.d.mts +378 -0
  173. package/dist/validate-CpBtVMsD.d.mts.map +1 -0
  174. package/dist/validate-CqRJb_xU.mjs +97 -0
  175. package/dist/validate-CqRJb_xU.mjs.map +1 -0
  176. package/dist/validate-O7PWmlnq.mjs +328 -0
  177. package/dist/validate-O7PWmlnq.mjs.map +1 -0
  178. package/locals.d.ts +46 -0
  179. package/package.json +233 -19
  180. package/src/api/authorize.ts +63 -0
  181. package/src/api/csrf.ts +48 -0
  182. package/src/api/error.ts +99 -0
  183. package/src/api/errors.ts +445 -0
  184. package/src/api/escape.ts +9 -0
  185. package/src/api/handlers/api-tokens.ts +240 -0
  186. package/src/api/handlers/comments.ts +314 -0
  187. package/src/api/handlers/content.ts +1315 -0
  188. package/src/api/handlers/dashboard.ts +205 -0
  189. package/src/api/handlers/device-flow.ts +684 -0
  190. package/src/api/handlers/index.ts +163 -0
  191. package/src/api/handlers/manifest.ts +158 -0
  192. package/src/api/handlers/marketplace.ts +930 -0
  193. package/src/api/handlers/media.ts +207 -0
  194. package/src/api/handlers/menus.ts +493 -0
  195. package/src/api/handlers/oauth-authorization.ts +429 -0
  196. package/src/api/handlers/oauth-clients.ts +349 -0
  197. package/src/api/handlers/oauth-user-lookup.ts +39 -0
  198. package/src/api/handlers/plugins.ts +254 -0
  199. package/src/api/handlers/redirects.ts +360 -0
  200. package/src/api/handlers/revision.ts +145 -0
  201. package/src/api/handlers/schema.ts +534 -0
  202. package/src/api/handlers/sections.ts +289 -0
  203. package/src/api/handlers/seo.ts +115 -0
  204. package/src/api/handlers/settings.ts +49 -0
  205. package/src/api/handlers/snapshot.ts +350 -0
  206. package/src/api/handlers/taxonomies.ts +523 -0
  207. package/src/api/index.ts +6 -0
  208. package/src/api/openapi/document.ts +2368 -0
  209. package/src/api/openapi/index.ts +1 -0
  210. package/src/api/parse.ts +139 -0
  211. package/src/api/redirect.ts +14 -0
  212. package/src/api/rev.ts +67 -0
  213. package/src/api/schemas/auth.ts +112 -0
  214. package/src/api/schemas/bylines.ts +85 -0
  215. package/src/api/schemas/comments.ts +117 -0
  216. package/src/api/schemas/common.ts +89 -0
  217. package/src/api/schemas/content.ts +191 -0
  218. package/src/api/schemas/import.ts +52 -0
  219. package/src/api/schemas/index.ts +17 -0
  220. package/src/api/schemas/media.ts +116 -0
  221. package/src/api/schemas/menus.ts +111 -0
  222. package/src/api/schemas/redirects.ts +155 -0
  223. package/src/api/schemas/schema.ts +203 -0
  224. package/src/api/schemas/search.ts +63 -0
  225. package/src/api/schemas/sections.ts +67 -0
  226. package/src/api/schemas/settings.ts +63 -0
  227. package/src/api/schemas/setup.ts +37 -0
  228. package/src/api/schemas/taxonomies.ts +113 -0
  229. package/src/api/schemas/users.ts +96 -0
  230. package/src/api/schemas/widgets.ts +80 -0
  231. package/src/api/site-url.ts +25 -0
  232. package/src/api/types.ts +82 -0
  233. package/src/astro/index.ts +27 -0
  234. package/src/astro/integration/index.ts +303 -0
  235. package/src/astro/integration/routes.ts +834 -0
  236. package/src/astro/integration/runtime.ts +338 -0
  237. package/src/astro/integration/virtual-modules.ts +469 -0
  238. package/src/astro/integration/vite-config.ts +335 -0
  239. package/src/astro/middleware/auth.ts +743 -0
  240. package/src/astro/middleware/redirect.ts +89 -0
  241. package/src/astro/middleware/request-context.ts +129 -0
  242. package/src/astro/middleware/setup.ts +89 -0
  243. package/src/astro/middleware.ts +398 -0
  244. package/src/astro/routes/PluginRegistry.tsx +15 -0
  245. package/src/astro/routes/admin.astro +81 -0
  246. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
  247. package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
  248. package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
  249. package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
  250. package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
  251. package/src/astro/routes/api/admin/bylines/index.ts +72 -0
  252. package/src/astro/routes/api/admin/comments/[id]/status.ts +116 -0
  253. package/src/astro/routes/api/admin/comments/[id].ts +64 -0
  254. package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
  255. package/src/astro/routes/api/admin/comments/counts.ts +30 -0
  256. package/src/astro/routes/api/admin/comments/index.ts +46 -0
  257. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
  258. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
  259. package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
  260. package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
  261. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
  262. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
  263. package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
  264. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
  265. package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
  266. package/src/astro/routes/api/admin/plugins/index.ts +32 -0
  267. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +61 -0
  268. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
  269. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +62 -0
  270. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
  271. package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
  272. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
  273. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +61 -0
  274. package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
  275. package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
  276. package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
  277. package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
  278. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
  279. package/src/astro/routes/api/admin/users/index.ts +66 -0
  280. package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
  281. package/src/astro/routes/api/auth/invite/accept.ts +52 -0
  282. package/src/astro/routes/api/auth/invite/complete.ts +84 -0
  283. package/src/astro/routes/api/auth/invite/index.ts +99 -0
  284. package/src/astro/routes/api/auth/logout.ts +40 -0
  285. package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
  286. package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
  287. package/src/astro/routes/api/auth/me.ts +60 -0
  288. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +219 -0
  289. package/src/astro/routes/api/auth/oauth/[provider].ts +119 -0
  290. package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
  291. package/src/astro/routes/api/auth/passkey/index.ts +54 -0
  292. package/src/astro/routes/api/auth/passkey/options.ts +82 -0
  293. package/src/astro/routes/api/auth/passkey/register/options.ts +86 -0
  294. package/src/astro/routes/api/auth/passkey/register/verify.ts +115 -0
  295. package/src/astro/routes/api/auth/passkey/verify.ts +66 -0
  296. package/src/astro/routes/api/auth/signup/complete.ts +85 -0
  297. package/src/astro/routes/api/auth/signup/request.ts +77 -0
  298. package/src/astro/routes/api/auth/signup/verify.ts +53 -0
  299. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +312 -0
  300. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
  301. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
  302. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
  303. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
  304. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
  305. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
  306. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
  307. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
  308. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +101 -0
  309. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
  310. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
  311. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
  312. package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
  313. package/src/astro/routes/api/content/[collection]/index.ts +59 -0
  314. package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
  315. package/src/astro/routes/api/dashboard.ts +32 -0
  316. package/src/astro/routes/api/dev/emails.ts +36 -0
  317. package/src/astro/routes/api/import/probe.ts +47 -0
  318. package/src/astro/routes/api/import/wordpress/analyze.ts +510 -0
  319. package/src/astro/routes/api/import/wordpress/execute.ts +283 -0
  320. package/src/astro/routes/api/import/wordpress/media.ts +338 -0
  321. package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
  322. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
  323. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
  324. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
  325. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +347 -0
  326. package/src/astro/routes/api/manifest.ts +62 -0
  327. package/src/astro/routes/api/mcp.ts +124 -0
  328. package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
  329. package/src/astro/routes/api/media/[id].ts +145 -0
  330. package/src/astro/routes/api/media/file/[key].ts +79 -0
  331. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
  332. package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
  333. package/src/astro/routes/api/media/providers/index.ts +30 -0
  334. package/src/astro/routes/api/media/upload-url.ts +137 -0
  335. package/src/astro/routes/api/media.ts +190 -0
  336. package/src/astro/routes/api/menus/[name]/items.ts +87 -0
  337. package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
  338. package/src/astro/routes/api/menus/[name].ts +65 -0
  339. package/src/astro/routes/api/menus/index.ts +47 -0
  340. package/src/astro/routes/api/oauth/authorize.ts +412 -0
  341. package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
  342. package/src/astro/routes/api/oauth/device/code.ts +51 -0
  343. package/src/astro/routes/api/oauth/device/token.ts +69 -0
  344. package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
  345. package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
  346. package/src/astro/routes/api/oauth/token.ts +184 -0
  347. package/src/astro/routes/api/openapi.json.ts +32 -0
  348. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
  349. package/src/astro/routes/api/redirects/404s/index.ts +72 -0
  350. package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
  351. package/src/astro/routes/api/redirects/[id].ts +84 -0
  352. package/src/astro/routes/api/redirects/index.ts +52 -0
  353. package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
  354. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +58 -0
  355. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
  356. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
  357. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
  358. package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
  359. package/src/astro/routes/api/schema/collections/index.ts +47 -0
  360. package/src/astro/routes/api/schema/index.ts +109 -0
  361. package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
  362. package/src/astro/routes/api/schema/orphans/index.ts +26 -0
  363. package/src/astro/routes/api/search/enable.ts +64 -0
  364. package/src/astro/routes/api/search/index.ts +55 -0
  365. package/src/astro/routes/api/search/rebuild.ts +72 -0
  366. package/src/astro/routes/api/search/stats.ts +35 -0
  367. package/src/astro/routes/api/search/suggest.ts +53 -0
  368. package/src/astro/routes/api/sections/[slug].ts +84 -0
  369. package/src/astro/routes/api/sections/index.ts +52 -0
  370. package/src/astro/routes/api/settings/email.ts +150 -0
  371. package/src/astro/routes/api/settings.ts +67 -0
  372. package/src/astro/routes/api/setup/admin-verify.ts +100 -0
  373. package/src/astro/routes/api/setup/admin.ts +94 -0
  374. package/src/astro/routes/api/setup/dev-bypass.ts +199 -0
  375. package/src/astro/routes/api/setup/dev-reset.ts +40 -0
  376. package/src/astro/routes/api/setup/index.ts +126 -0
  377. package/src/astro/routes/api/setup/status.ts +122 -0
  378. package/src/astro/routes/api/snapshot.ts +75 -0
  379. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
  380. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
  381. package/src/astro/routes/api/taxonomies/index.ts +59 -0
  382. package/src/astro/routes/api/themes/preview.ts +77 -0
  383. package/src/astro/routes/api/typegen.ts +114 -0
  384. package/src/astro/routes/api/well-known/auth.ts +68 -0
  385. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +44 -0
  386. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +37 -0
  387. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +68 -0
  388. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
  389. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
  390. package/src/astro/routes/api/widget-areas/[name].ts +87 -0
  391. package/src/astro/routes/api/widget-areas/index.ts +99 -0
  392. package/src/astro/routes/api/widget-components.ts +22 -0
  393. package/src/astro/routes/robots.txt.ts +77 -0
  394. package/src/astro/routes/sitemap.xml.ts +97 -0
  395. package/src/astro/storage/adapters.ts +74 -0
  396. package/src/astro/storage/index.ts +19 -0
  397. package/src/astro/storage/types.ts +60 -0
  398. package/src/astro/types.ts +346 -0
  399. package/src/auth/api-tokens.ts +25 -0
  400. package/src/auth/challenge-store.ts +80 -0
  401. package/src/auth/mode.ts +96 -0
  402. package/src/auth/oauth-state-store.ts +96 -0
  403. package/src/auth/passkey-config.ts +27 -0
  404. package/src/auth/rate-limit.ts +158 -0
  405. package/src/auth/scopes.ts +33 -0
  406. package/src/auth/types.ts +104 -0
  407. package/src/aws-sdk.d.ts +100 -0
  408. package/src/bylines/index.ts +237 -0
  409. package/src/cleanup.ts +153 -0
  410. package/src/cli/client-factory.ts +100 -0
  411. package/src/cli/commands/auth.ts +46 -0
  412. package/src/cli/commands/bundle-utils.ts +247 -0
  413. package/src/cli/commands/bundle.ts +609 -0
  414. package/src/cli/commands/content.ts +442 -0
  415. package/src/cli/commands/dev.ts +191 -0
  416. package/src/cli/commands/doctor.ts +211 -0
  417. package/src/cli/commands/export-seed.ts +630 -0
  418. package/src/cli/commands/import/wordpress.ts +1056 -0
  419. package/src/cli/commands/init.ts +192 -0
  420. package/src/cli/commands/login.ts +547 -0
  421. package/src/cli/commands/media.ts +165 -0
  422. package/src/cli/commands/menu.ts +67 -0
  423. package/src/cli/commands/plugin-init.ts +291 -0
  424. package/src/cli/commands/plugin-validate.ts +31 -0
  425. package/src/cli/commands/plugin.ts +33 -0
  426. package/src/cli/commands/publish.ts +697 -0
  427. package/src/cli/commands/schema.ts +233 -0
  428. package/src/cli/commands/search-cmd.ts +54 -0
  429. package/src/cli/commands/seed.ts +286 -0
  430. package/src/cli/commands/taxonomy.ts +128 -0
  431. package/src/cli/commands/types.ts +68 -0
  432. package/src/cli/credentials.ts +236 -0
  433. package/src/cli/index.ts +70 -0
  434. package/src/cli/output.ts +75 -0
  435. package/src/cli/wxr/parser.ts +969 -0
  436. package/src/client/cf-access.ts +193 -0
  437. package/src/client/index.ts +854 -0
  438. package/src/client/portable-text.ts +413 -0
  439. package/src/client/transport.ts +200 -0
  440. package/src/comments/moderator.ts +46 -0
  441. package/src/comments/notifications.ts +144 -0
  442. package/src/comments/query.ts +105 -0
  443. package/src/comments/service.ts +213 -0
  444. package/src/components/Break.astro +45 -0
  445. package/src/components/Button.astro +71 -0
  446. package/src/components/Buttons.astro +49 -0
  447. package/src/components/Code.astro +59 -0
  448. package/src/components/Columns.astro +59 -0
  449. package/src/components/CommentForm.astro +315 -0
  450. package/src/components/Comments.astro +232 -0
  451. package/src/components/Cover.astro +128 -0
  452. package/src/components/EmDashBodyEnd.astro +32 -0
  453. package/src/components/EmDashBodyStart.astro +32 -0
  454. package/src/components/EmDashHead.astro +53 -0
  455. package/src/components/EmDashImage.astro +178 -0
  456. package/src/components/EmDashMedia.astro +167 -0
  457. package/src/components/Embed.astro +128 -0
  458. package/src/components/File.astro +122 -0
  459. package/src/components/Gallery.astro +93 -0
  460. package/src/components/HtmlBlock.astro +33 -0
  461. package/src/components/Image.astro +178 -0
  462. package/src/components/InlineEditor.astro +27 -0
  463. package/src/components/InlinePortableTextEditor.tsx +1905 -0
  464. package/src/components/LiveSearch.astro +614 -0
  465. package/src/components/PortableText.astro +51 -0
  466. package/src/components/Pullquote.astro +51 -0
  467. package/src/components/Table.astro +108 -0
  468. package/src/components/WidgetArea.astro +22 -0
  469. package/src/components/WidgetRenderer.astro +72 -0
  470. package/src/components/index.ts +116 -0
  471. package/src/components/marks/Link.astro +31 -0
  472. package/src/components/marks/StrikeThrough.astro +7 -0
  473. package/src/components/marks/Subscript.astro +7 -0
  474. package/src/components/marks/Superscript.astro +7 -0
  475. package/src/components/marks/Underline.astro +7 -0
  476. package/src/components/widgets/Archives.astro +65 -0
  477. package/src/components/widgets/Categories.astro +35 -0
  478. package/src/components/widgets/RecentPosts.astro +51 -0
  479. package/src/components/widgets/Search.astro +18 -0
  480. package/src/components/widgets/Tags.astro +38 -0
  481. package/src/content/converters/index.ts +9 -0
  482. package/src/content/converters/portable-text-to-prosemirror.ts +385 -0
  483. package/src/content/converters/prosemirror-to-portable-text.ts +413 -0
  484. package/src/content/converters/types.ts +120 -0
  485. package/src/content/index.ts +5 -0
  486. package/src/database/connection.ts +67 -0
  487. package/src/database/dialect-helpers.ts +138 -0
  488. package/src/database/index.ts +5 -0
  489. package/src/database/migrations/001_initial.ts +170 -0
  490. package/src/database/migrations/002_media_status.ts +26 -0
  491. package/src/database/migrations/003_schema_registry.ts +79 -0
  492. package/src/database/migrations/004_plugins.ts +62 -0
  493. package/src/database/migrations/005_menus.ts +67 -0
  494. package/src/database/migrations/006_taxonomy_defs.ts +51 -0
  495. package/src/database/migrations/007_widgets.ts +42 -0
  496. package/src/database/migrations/008_auth.ts +194 -0
  497. package/src/database/migrations/009_user_disabled.ts +27 -0
  498. package/src/database/migrations/011_sections.ts +65 -0
  499. package/src/database/migrations/012_search.ts +25 -0
  500. package/src/database/migrations/013_scheduled_publishing.ts +51 -0
  501. package/src/database/migrations/014_draft_revisions.ts +72 -0
  502. package/src/database/migrations/015_indexes.ts +82 -0
  503. package/src/database/migrations/016_api_tokens.ts +89 -0
  504. package/src/database/migrations/017_authorization_codes.ts +45 -0
  505. package/src/database/migrations/018_seo.ts +56 -0
  506. package/src/database/migrations/019_i18n.ts +618 -0
  507. package/src/database/migrations/020_collection_url_pattern.ts +23 -0
  508. package/src/database/migrations/021_remove_section_categories.ts +43 -0
  509. package/src/database/migrations/022_marketplace_plugin_state.ts +46 -0
  510. package/src/database/migrations/023_plugin_metadata.ts +33 -0
  511. package/src/database/migrations/024_media_placeholders.ts +32 -0
  512. package/src/database/migrations/025_oauth_clients.ts +28 -0
  513. package/src/database/migrations/026_cron_tasks.ts +49 -0
  514. package/src/database/migrations/027_comments.ts +87 -0
  515. package/src/database/migrations/028_drop_author_url.ts +9 -0
  516. package/src/database/migrations/029_redirects.ts +67 -0
  517. package/src/database/migrations/030_widen_scheduled_index.ts +48 -0
  518. package/src/database/migrations/031_bylines.ts +90 -0
  519. package/src/database/migrations/032_rate_limits.ts +39 -0
  520. package/src/database/migrations/runner.ts +170 -0
  521. package/src/database/repositories/audit.ts +294 -0
  522. package/src/database/repositories/byline.ts +387 -0
  523. package/src/database/repositories/comment.ts +458 -0
  524. package/src/database/repositories/content.ts +1144 -0
  525. package/src/database/repositories/index.ts +30 -0
  526. package/src/database/repositories/media.ts +347 -0
  527. package/src/database/repositories/options.ts +150 -0
  528. package/src/database/repositories/plugin-storage.ts +373 -0
  529. package/src/database/repositories/redirect.ts +480 -0
  530. package/src/database/repositories/revision.ts +200 -0
  531. package/src/database/repositories/seo.ts +176 -0
  532. package/src/database/repositories/taxonomy.ts +294 -0
  533. package/src/database/repositories/types.ts +132 -0
  534. package/src/database/repositories/user.ts +258 -0
  535. package/src/database/transaction.ts +54 -0
  536. package/src/database/types.ts +501 -0
  537. package/src/database/validate.ts +138 -0
  538. package/src/db/adapters.ts +125 -0
  539. package/src/db/index.ts +37 -0
  540. package/src/db/libsql.ts +23 -0
  541. package/src/db/postgres.ts +30 -0
  542. package/src/db/sqlite.ts +27 -0
  543. package/src/emdash-runtime.ts +2096 -0
  544. package/src/fields/boolean.ts +34 -0
  545. package/src/fields/datetime.ts +44 -0
  546. package/src/fields/file.ts +41 -0
  547. package/src/fields/image.ts +34 -0
  548. package/src/fields/index.ts +42 -0
  549. package/src/fields/integer.ts +50 -0
  550. package/src/fields/json.ts +37 -0
  551. package/src/fields/multiselect.ts +48 -0
  552. package/src/fields/number.ts +52 -0
  553. package/src/fields/portable-text.ts +33 -0
  554. package/src/fields/reference.ts +29 -0
  555. package/src/fields/richtext.ts +31 -0
  556. package/src/fields/select.ts +46 -0
  557. package/src/fields/slug.ts +38 -0
  558. package/src/fields/text.ts +55 -0
  559. package/src/fields/textarea.ts +52 -0
  560. package/src/fields/types.ts +64 -0
  561. package/src/i18n/config.ts +68 -0
  562. package/src/import/index.ts +90 -0
  563. package/src/import/menus.ts +436 -0
  564. package/src/import/registry.ts +111 -0
  565. package/src/import/sections.ts +103 -0
  566. package/src/import/settings.ts +281 -0
  567. package/src/import/sources/wordpress-plugin.ts +641 -0
  568. package/src/import/sources/wordpress-rest.ts +191 -0
  569. package/src/import/sources/wxr.ts +330 -0
  570. package/src/import/ssrf.ts +260 -0
  571. package/src/import/types.ts +418 -0
  572. package/src/import/utils.ts +412 -0
  573. package/src/index.ts +481 -0
  574. package/src/loader.ts +770 -0
  575. package/src/mcp/server.ts +1463 -0
  576. package/src/media/index.ts +32 -0
  577. package/src/media/local-runtime.ts +213 -0
  578. package/src/media/local.ts +46 -0
  579. package/src/media/normalize.ts +190 -0
  580. package/src/media/placeholder.ts +150 -0
  581. package/src/media/provider-loader.ts +78 -0
  582. package/src/media/types.ts +279 -0
  583. package/src/menus/index.ts +324 -0
  584. package/src/menus/types.ts +112 -0
  585. package/src/page/context.ts +93 -0
  586. package/src/page/fragments.ts +89 -0
  587. package/src/page/index.ts +58 -0
  588. package/src/page/jsonld.ts +94 -0
  589. package/src/page/metadata.ts +185 -0
  590. package/src/page/seo-contributions.ts +136 -0
  591. package/src/plugin-utils.ts +80 -0
  592. package/src/plugins/adapt-sandbox-entry.ts +207 -0
  593. package/src/plugins/context.ts +833 -0
  594. package/src/plugins/cron.ts +361 -0
  595. package/src/plugins/define-plugin.ts +259 -0
  596. package/src/plugins/email-console.ts +73 -0
  597. package/src/plugins/email.ts +209 -0
  598. package/src/plugins/hooks.ts +1273 -0
  599. package/src/plugins/index.ts +193 -0
  600. package/src/plugins/manager.ts +595 -0
  601. package/src/plugins/manifest-schema.ts +230 -0
  602. package/src/plugins/marketplace.ts +460 -0
  603. package/src/plugins/request-meta.ts +139 -0
  604. package/src/plugins/routes.ts +302 -0
  605. package/src/plugins/sandbox/index.ts +18 -0
  606. package/src/plugins/sandbox/noop.ts +76 -0
  607. package/src/plugins/sandbox/types.ts +173 -0
  608. package/src/plugins/scheduler/node.ts +122 -0
  609. package/src/plugins/scheduler/piggyback.ts +71 -0
  610. package/src/plugins/scheduler/types.ts +27 -0
  611. package/src/plugins/state.ts +208 -0
  612. package/src/plugins/storage-indexes.ts +326 -0
  613. package/src/plugins/storage-query.ts +240 -0
  614. package/src/plugins/types.ts +1284 -0
  615. package/src/preview/helpers.ts +27 -0
  616. package/src/preview/index.ts +40 -0
  617. package/src/preview/tokens.ts +279 -0
  618. package/src/preview/urls.ts +118 -0
  619. package/src/query.ts +674 -0
  620. package/src/redirects/patterns.ts +224 -0
  621. package/src/request-context.ts +67 -0
  622. package/src/runtime.ts +21 -0
  623. package/src/schema/index.ts +29 -0
  624. package/src/schema/query.ts +44 -0
  625. package/src/schema/registry.ts +965 -0
  626. package/src/schema/types.ts +276 -0
  627. package/src/schema/zod-generator.ts +413 -0
  628. package/src/search/fts-manager.ts +452 -0
  629. package/src/search/index.ts +26 -0
  630. package/src/search/query.ts +396 -0
  631. package/src/search/text-extraction.ts +162 -0
  632. package/src/search/types.ts +114 -0
  633. package/src/sections/index.ts +226 -0
  634. package/src/sections/types.ts +86 -0
  635. package/src/seed/apply.ts +1141 -0
  636. package/src/seed/default.ts +86 -0
  637. package/src/seed/index.ts +28 -0
  638. package/src/seed/load.ts +35 -0
  639. package/src/seed/types.ts +341 -0
  640. package/src/seed/validate.ts +642 -0
  641. package/src/seo/index.ts +179 -0
  642. package/src/settings/index.ts +203 -0
  643. package/src/settings/types.ts +58 -0
  644. package/src/storage/index.ts +28 -0
  645. package/src/storage/local.ts +249 -0
  646. package/src/storage/s3.ts +263 -0
  647. package/src/storage/types.ts +204 -0
  648. package/src/taxonomies/index.ts +309 -0
  649. package/src/taxonomies/types.ts +61 -0
  650. package/src/ui.ts +75 -0
  651. package/src/utils/base64.ts +73 -0
  652. package/src/utils/hash.ts +36 -0
  653. package/src/utils/sanitize.ts +20 -0
  654. package/src/utils/slugify.ts +29 -0
  655. package/src/utils/url.ts +48 -0
  656. package/src/virtual-modules.d.ts +111 -0
  657. package/src/visual-editing/editable.ts +108 -0
  658. package/src/visual-editing/toolbar.ts +1229 -0
  659. package/src/widgets/components.ts +105 -0
  660. package/src/widgets/index.ts +131 -0
  661. package/src/widgets/types.ts +81 -0
@@ -0,0 +1,1293 @@
1
+ import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
2
+ import { t as ContentRepository } from "./content-D6C2WsZC.mjs";
3
+ import { t as MediaRepository } from "./media-DqHVh136.mjs";
4
+ import { i as FTSManager, n as SchemaRegistry } from "./registry-D_w5HW4G.mjs";
5
+ import { t as RedirectRepository } from "./redirect-DIfIni3r.mjs";
6
+ import { t as BylineRepository } from "./byline-CL847F26.mjs";
7
+ import { n as getDb } from "./loader-fz8Q_3EO.mjs";
8
+ import { t as validateSeed } from "./validate-O7PWmlnq.mjs";
9
+ import { ulid } from "ulidx";
10
+ import mime from "mime/lite";
11
+ import { imageSize } from "image-size";
12
+
13
+ //#region src/database/repositories/taxonomy.ts
14
+ /**
15
+ * Taxonomy repository for categories, tags, and other classification
16
+ *
17
+ * Taxonomies are hierarchical (via parentId) and can be attached to content entries.
18
+ */
19
+ var TaxonomyRepository = class {
20
+ constructor(db) {
21
+ this.db = db;
22
+ }
23
+ /**
24
+ * Create a new taxonomy term
25
+ */
26
+ async create(input) {
27
+ const id = ulid();
28
+ const row = {
29
+ id,
30
+ name: input.name,
31
+ slug: input.slug,
32
+ label: input.label,
33
+ parent_id: input.parentId ?? null,
34
+ data: input.data ? JSON.stringify(input.data) : null
35
+ };
36
+ await this.db.insertInto("taxonomies").values(row).execute();
37
+ const taxonomy = await this.findById(id);
38
+ if (!taxonomy) throw new Error("Failed to create taxonomy");
39
+ return taxonomy;
40
+ }
41
+ /**
42
+ * Find taxonomy by ID
43
+ */
44
+ async findById(id) {
45
+ const row = await this.db.selectFrom("taxonomies").selectAll().where("id", "=", id).executeTakeFirst();
46
+ return row ? this.rowToTaxonomy(row) : null;
47
+ }
48
+ /**
49
+ * Find taxonomy by name and slug (unique constraint)
50
+ */
51
+ async findBySlug(name, slug) {
52
+ const row = await this.db.selectFrom("taxonomies").selectAll().where("name", "=", name).where("slug", "=", slug).executeTakeFirst();
53
+ return row ? this.rowToTaxonomy(row) : null;
54
+ }
55
+ /**
56
+ * Get all terms for a taxonomy (e.g., all categories)
57
+ */
58
+ async findByName(name, options = {}) {
59
+ let query = this.db.selectFrom("taxonomies").selectAll().where("name", "=", name).orderBy("label", "asc");
60
+ if (options.parentId !== void 0) if (options.parentId === null) query = query.where("parent_id", "is", null);
61
+ else query = query.where("parent_id", "=", options.parentId);
62
+ return (await query.execute()).map((row) => this.rowToTaxonomy(row));
63
+ }
64
+ /**
65
+ * Get children of a taxonomy term
66
+ */
67
+ async findChildren(parentId) {
68
+ return (await this.db.selectFrom("taxonomies").selectAll().where("parent_id", "=", parentId).orderBy("label", "asc").execute()).map((row) => this.rowToTaxonomy(row));
69
+ }
70
+ /**
71
+ * Update a taxonomy term
72
+ */
73
+ async update(id, input) {
74
+ if (!await this.findById(id)) return null;
75
+ const updates = {};
76
+ if (input.slug !== void 0) updates.slug = input.slug;
77
+ if (input.label !== void 0) updates.label = input.label;
78
+ if (input.parentId !== void 0) updates.parent_id = input.parentId;
79
+ if (input.data !== void 0) updates.data = JSON.stringify(input.data);
80
+ if (Object.keys(updates).length > 0) await this.db.updateTable("taxonomies").set(updates).where("id", "=", id).execute();
81
+ return this.findById(id);
82
+ }
83
+ /**
84
+ * Delete a taxonomy term
85
+ */
86
+ async delete(id) {
87
+ await this.db.deleteFrom("content_taxonomies").where("taxonomy_id", "=", id).execute();
88
+ return ((await this.db.deleteFrom("taxonomies").where("id", "=", id).executeTakeFirst()).numDeletedRows ?? 0) > 0;
89
+ }
90
+ /**
91
+ * Attach a taxonomy term to a content entry
92
+ */
93
+ async attachToEntry(collection, entryId, taxonomyId) {
94
+ const row = {
95
+ collection,
96
+ entry_id: entryId,
97
+ taxonomy_id: taxonomyId
98
+ };
99
+ await this.db.insertInto("content_taxonomies").values(row).onConflict((oc) => oc.doNothing()).execute();
100
+ }
101
+ /**
102
+ * Detach a taxonomy term from a content entry
103
+ */
104
+ async detachFromEntry(collection, entryId, taxonomyId) {
105
+ await this.db.deleteFrom("content_taxonomies").where("collection", "=", collection).where("entry_id", "=", entryId).where("taxonomy_id", "=", taxonomyId).execute();
106
+ }
107
+ /**
108
+ * Get all taxonomy terms for a content entry
109
+ */
110
+ async getTermsForEntry(collection, entryId, taxonomyName) {
111
+ let query = this.db.selectFrom("content_taxonomies").innerJoin("taxonomies", "taxonomies.id", "content_taxonomies.taxonomy_id").selectAll("taxonomies").where("content_taxonomies.collection", "=", collection).where("content_taxonomies.entry_id", "=", entryId);
112
+ if (taxonomyName) query = query.where("taxonomies.name", "=", taxonomyName);
113
+ return (await query.execute()).map((row) => this.rowToTaxonomy(row));
114
+ }
115
+ /**
116
+ * Set all taxonomy terms for a content entry (replaces existing)
117
+ * Uses batch operations to avoid N+1 queries.
118
+ */
119
+ async setTermsForEntry(collection, entryId, taxonomyName, taxonomyIds) {
120
+ const current = await this.getTermsForEntry(collection, entryId, taxonomyName);
121
+ const currentIds = new Set(current.map((t) => t.id));
122
+ const newIds = new Set(taxonomyIds);
123
+ const toRemove = current.filter((t) => !newIds.has(t.id)).map((t) => t.id);
124
+ if (toRemove.length > 0) await this.db.deleteFrom("content_taxonomies").where("collection", "=", collection).where("entry_id", "=", entryId).where("taxonomy_id", "in", toRemove).execute();
125
+ const toAdd = taxonomyIds.filter((id) => !currentIds.has(id));
126
+ if (toAdd.length > 0) await this.db.insertInto("content_taxonomies").values(toAdd.map((taxonomy_id) => ({
127
+ collection,
128
+ entry_id: entryId,
129
+ taxonomy_id
130
+ }))).onConflict((oc) => oc.doNothing()).execute();
131
+ }
132
+ /**
133
+ * Remove all taxonomy associations for an entry (use when entry is deleted)
134
+ */
135
+ async clearEntryTerms(collection, entryId) {
136
+ const result = await this.db.deleteFrom("content_taxonomies").where("collection", "=", collection).where("entry_id", "=", entryId).executeTakeFirst();
137
+ return Number(result.numDeletedRows ?? 0);
138
+ }
139
+ /**
140
+ * Count entries that have a specific taxonomy term
141
+ */
142
+ async countEntriesWithTerm(taxonomyId) {
143
+ const result = await this.db.selectFrom("content_taxonomies").select((eb) => eb.fn.count("entry_id").as("count")).where("taxonomy_id", "=", taxonomyId).executeTakeFirst();
144
+ return Number(result?.count || 0);
145
+ }
146
+ /**
147
+ * Convert database row to Taxonomy object
148
+ */
149
+ rowToTaxonomy(row) {
150
+ return {
151
+ id: row.id,
152
+ name: row.name,
153
+ slug: row.slug,
154
+ label: row.label,
155
+ parentId: row.parent_id,
156
+ data: row.data ? JSON.parse(row.data) : null
157
+ };
158
+ }
159
+ };
160
+
161
+ //#endregion
162
+ //#region src/database/repositories/options.ts
163
+ /**
164
+ * Options repository for key-value settings storage
165
+ *
166
+ * Used for site settings, plugin configuration, and other arbitrary key-value data.
167
+ * Values are stored as JSON for flexibility.
168
+ */
169
+ var OptionsRepository = class {
170
+ constructor(db) {
171
+ this.db = db;
172
+ }
173
+ /**
174
+ * Get an option value
175
+ */
176
+ async get(name) {
177
+ const row = await this.db.selectFrom("options").select("value").where("name", "=", name).executeTakeFirst();
178
+ if (!row) return null;
179
+ return JSON.parse(row.value);
180
+ }
181
+ /**
182
+ * Get an option value with a default
183
+ */
184
+ async getOrDefault(name, defaultValue) {
185
+ return await this.get(name) ?? defaultValue;
186
+ }
187
+ /**
188
+ * Set an option value (creates or updates)
189
+ */
190
+ async set(name, value) {
191
+ const row = {
192
+ name,
193
+ value: JSON.stringify(value)
194
+ };
195
+ await this.db.insertInto("options").values(row).onConflict((oc) => oc.column("name").doUpdateSet({ value: row.value })).execute();
196
+ }
197
+ /**
198
+ * Delete an option
199
+ */
200
+ async delete(name) {
201
+ return ((await this.db.deleteFrom("options").where("name", "=", name).executeTakeFirst()).numDeletedRows ?? 0) > 0;
202
+ }
203
+ /**
204
+ * Check if an option exists
205
+ */
206
+ async exists(name) {
207
+ return !!await this.db.selectFrom("options").select("name").where("name", "=", name).executeTakeFirst();
208
+ }
209
+ /**
210
+ * Get multiple options at once
211
+ */
212
+ async getMany(names) {
213
+ if (names.length === 0) return /* @__PURE__ */ new Map();
214
+ const rows = await this.db.selectFrom("options").select(["name", "value"]).where("name", "in", names).execute();
215
+ const result = /* @__PURE__ */ new Map();
216
+ for (const row of rows) result.set(row.name, JSON.parse(row.value));
217
+ return result;
218
+ }
219
+ /**
220
+ * Set multiple options at once
221
+ */
222
+ async setMany(options) {
223
+ const entries = Object.entries(options);
224
+ if (entries.length === 0) return;
225
+ for (const [name, value] of entries) await this.set(name, value);
226
+ }
227
+ /**
228
+ * Get all options (use sparingly)
229
+ */
230
+ async getAll() {
231
+ const rows = await this.db.selectFrom("options").select(["name", "value"]).execute();
232
+ const result = /* @__PURE__ */ new Map();
233
+ for (const row of rows) result.set(row.name, JSON.parse(row.value));
234
+ return result;
235
+ }
236
+ /**
237
+ * Get all options matching a prefix
238
+ */
239
+ async getByPrefix(prefix) {
240
+ const rows = await this.db.selectFrom("options").select(["name", "value"]).where("name", "like", `${prefix}%`).execute();
241
+ const result = /* @__PURE__ */ new Map();
242
+ for (const row of rows) result.set(row.name, JSON.parse(row.value));
243
+ return result;
244
+ }
245
+ /**
246
+ * Delete all options matching a prefix
247
+ */
248
+ async deleteByPrefix(prefix) {
249
+ const result = await this.db.deleteFrom("options").where("name", "like", `${prefix}%`).executeTakeFirst();
250
+ return Number(result.numDeletedRows ?? 0);
251
+ }
252
+ };
253
+
254
+ //#endregion
255
+ //#region src/settings/index.ts
256
+ /** Prefix for site settings in the options table */
257
+ const SETTINGS_PREFIX = "site:";
258
+ /**
259
+ * Type guard for MediaReference values
260
+ */
261
+ function isMediaReference(value) {
262
+ return typeof value === "object" && value !== null && "mediaId" in value;
263
+ }
264
+ /**
265
+ * Resolve a media reference to include the full URL
266
+ */
267
+ async function resolveMediaReference(mediaRef, db, _storage) {
268
+ if (!mediaRef?.mediaId) return mediaRef;
269
+ try {
270
+ const media = await new MediaRepository(db).findById(mediaRef.mediaId);
271
+ if (media) return {
272
+ ...mediaRef,
273
+ url: `/_emdash/api/media/file/${media.storageKey}`
274
+ };
275
+ } catch {}
276
+ return mediaRef;
277
+ }
278
+ /**
279
+ * Get a single site setting by key
280
+ *
281
+ * Returns `undefined` if the setting has not been configured.
282
+ * For media settings (logo, favicon), the URL is resolved automatically.
283
+ *
284
+ * @param key - The setting key (e.g., "title", "logo", "social")
285
+ * @returns The setting value, or undefined if not set
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * import { getSiteSetting } from "emdash";
290
+ *
291
+ * const title = await getSiteSetting("title");
292
+ * const logo = await getSiteSetting("logo");
293
+ * console.log(logo?.url); // Resolved URL
294
+ * ```
295
+ */
296
+ async function getSiteSetting(key) {
297
+ return getSiteSettingWithDb(key, await getDb());
298
+ }
299
+ /**
300
+ * Get a single site setting by key (with explicit db)
301
+ *
302
+ * @internal Use `getSiteSetting()` in templates. This variant is for admin routes
303
+ * that already have a database handle.
304
+ */
305
+ async function getSiteSettingWithDb(key, db, storage = null) {
306
+ const value = await new OptionsRepository(db).get(`${SETTINGS_PREFIX}${key}`);
307
+ if (!value) return;
308
+ if ((key === "logo" || key === "favicon") && isMediaReference(value)) return await resolveMediaReference(value, db, storage);
309
+ return value;
310
+ }
311
+ /**
312
+ * Get all site settings
313
+ *
314
+ * Returns all configured settings. Unset values are undefined.
315
+ * Media references (logo/favicon) are resolved to include URLs.
316
+ *
317
+ * @example
318
+ * ```ts
319
+ * import { getSiteSettings } from "emdash";
320
+ *
321
+ * const settings = await getSiteSettings();
322
+ * console.log(settings.title); // "My Site"
323
+ * console.log(settings.logo?.url); // "/_emdash/api/media/file/abc123"
324
+ * ```
325
+ */
326
+ async function getSiteSettings() {
327
+ return getSiteSettingsWithDb(await getDb());
328
+ }
329
+ /**
330
+ * Get all site settings (with explicit db)
331
+ *
332
+ * @internal Use `getSiteSettings()` in templates. This variant is for admin routes
333
+ * that already have a database handle.
334
+ */
335
+ async function getSiteSettingsWithDb(db, storage = null) {
336
+ const allOptions = await new OptionsRepository(db).getByPrefix(SETTINGS_PREFIX);
337
+ const settings = {};
338
+ for (const [key, value] of allOptions) {
339
+ const settingKey = key.replace(SETTINGS_PREFIX, "");
340
+ settings[settingKey] = value;
341
+ }
342
+ const typedSettings = settings;
343
+ if (typedSettings.logo) typedSettings.logo = await resolveMediaReference(typedSettings.logo, db, storage);
344
+ if (typedSettings.favicon) typedSettings.favicon = await resolveMediaReference(typedSettings.favicon, db, storage);
345
+ return typedSettings;
346
+ }
347
+ /**
348
+ * Set site settings (internal function used by admin API)
349
+ *
350
+ * Merges provided settings with existing ones. Only provided fields are updated.
351
+ * Media references should include just the mediaId; URLs are resolved on read.
352
+ *
353
+ * @param settings - Partial settings object with values to update
354
+ * @param db - Kysely database instance
355
+ * @returns Promise that resolves when settings are saved
356
+ *
357
+ * @internal
358
+ *
359
+ * @example
360
+ * ```ts
361
+ * // Update multiple settings at once
362
+ * await setSiteSettings({
363
+ * title: "My Site",
364
+ * tagline: "Welcome",
365
+ * logo: { mediaId: "med_123", alt: "Logo" }
366
+ * }, db);
367
+ * ```
368
+ */
369
+ async function setSiteSettings(settings, db) {
370
+ const options = new OptionsRepository(db);
371
+ const updates = {};
372
+ for (const [key, value] of Object.entries(settings)) if (value !== void 0) updates[`${SETTINGS_PREFIX}${key}`] = value;
373
+ await options.setMany(updates);
374
+ }
375
+
376
+ //#endregion
377
+ //#region src/import/ssrf.ts
378
+ /**
379
+ * SSRF protection for import URLs.
380
+ *
381
+ * Validates that URLs don't target internal/private network addresses.
382
+ * Applied before any fetch() call in the import pipeline.
383
+ */
384
+ const IPV4_MAPPED_IPV6_DOTTED_PATTERN = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i;
385
+ const IPV4_MAPPED_IPV6_HEX_PATTERN = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
386
+ const IPV4_TRANSLATED_HEX_PATTERN = /^::ffff:0:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
387
+ const IPV6_EXPANDED_MAPPED_PATTERN = /^0{0,4}:0{0,4}:0{0,4}:0{0,4}:0{0,4}:ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
388
+ /**
389
+ * IPv4-compatible (deprecated) addresses: ::XXXX:XXXX
390
+ *
391
+ * The WHATWG URL parser normalizes [::127.0.0.1] to [::7f00:1] (no ffff prefix).
392
+ * These are deprecated but still parsed, and bypass the ffff-based checks.
393
+ */
394
+ const IPV4_COMPATIBLE_HEX_PATTERN = /^::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
395
+ /**
396
+ * NAT64 prefix (RFC 6052): 64:ff9b::XXXX:XXXX
397
+ *
398
+ * Used by NAT64 gateways to embed IPv4 addresses in IPv6.
399
+ * [64:ff9b::127.0.0.1] normalizes to [64:ff9b::7f00:1].
400
+ */
401
+ const NAT64_HEX_PATTERN = /^64:ff9b::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
402
+ const IPV6_BRACKET_PATTERN = /^\[|\]$/g;
403
+ /**
404
+ * Private and reserved IP ranges that should never be fetched.
405
+ *
406
+ * Includes:
407
+ * - Loopback (127.0.0.0/8)
408
+ * - Private (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
409
+ * - Link-local (169.254.0.0/16)
410
+ * - Cloud metadata (169.254.169.254 — AWS/GCP/Azure)
411
+ * - IPv6 loopback and link-local
412
+ */
413
+ const BLOCKED_PATTERNS = [
414
+ {
415
+ start: ip4ToNum(127, 0, 0, 0),
416
+ end: ip4ToNum(127, 255, 255, 255)
417
+ },
418
+ {
419
+ start: ip4ToNum(10, 0, 0, 0),
420
+ end: ip4ToNum(10, 255, 255, 255)
421
+ },
422
+ {
423
+ start: ip4ToNum(172, 16, 0, 0),
424
+ end: ip4ToNum(172, 31, 255, 255)
425
+ },
426
+ {
427
+ start: ip4ToNum(192, 168, 0, 0),
428
+ end: ip4ToNum(192, 168, 255, 255)
429
+ },
430
+ {
431
+ start: ip4ToNum(169, 254, 0, 0),
432
+ end: ip4ToNum(169, 254, 255, 255)
433
+ },
434
+ {
435
+ start: ip4ToNum(0, 0, 0, 0),
436
+ end: ip4ToNum(0, 255, 255, 255)
437
+ }
438
+ ];
439
+ const BLOCKED_HOSTNAMES = new Set([
440
+ "localhost",
441
+ "metadata.google.internal",
442
+ "metadata.google",
443
+ "[::1]"
444
+ ]);
445
+ /** Blocked URL schemes */
446
+ const ALLOWED_SCHEMES = new Set(["http:", "https:"]);
447
+ function ip4ToNum(a, b, c, d) {
448
+ return (a << 24 | b << 16 | c << 8 | d) >>> 0;
449
+ }
450
+ function parseIpv4(ip) {
451
+ const parts = ip.split(".");
452
+ if (parts.length !== 4) return null;
453
+ const nums = parts.map(Number);
454
+ if (nums.some((n) => isNaN(n) || n < 0 || n > 255)) return null;
455
+ return ip4ToNum(nums[0], nums[1], nums[2], nums[3]);
456
+ }
457
+ /**
458
+ * Convert IPv4-mapped/translated IPv6 addresses from hex form back to IPv4.
459
+ *
460
+ * The WHATWG URL parser normalizes dotted-decimal to hex:
461
+ * [::ffff:127.0.0.1] -> [::ffff:7f00:1]
462
+ * [::ffff:169.254.169.254] -> [::ffff:a9fe:a9fe]
463
+ *
464
+ * Without this conversion, the hex forms bypass isPrivateIp() regex checks.
465
+ */
466
+ function normalizeIPv6MappedToIPv4(ip) {
467
+ let match = ip.match(IPV4_MAPPED_IPV6_HEX_PATTERN);
468
+ if (!match) match = ip.match(IPV4_TRANSLATED_HEX_PATTERN);
469
+ if (!match) match = ip.match(IPV6_EXPANDED_MAPPED_PATTERN);
470
+ if (!match) match = ip.match(IPV4_COMPATIBLE_HEX_PATTERN);
471
+ if (!match) match = ip.match(NAT64_HEX_PATTERN);
472
+ if (match) {
473
+ const high = parseInt(match[1] ?? "", 16);
474
+ const low = parseInt(match[2] ?? "", 16);
475
+ return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
476
+ }
477
+ return null;
478
+ }
479
+ function isPrivateIp(ip) {
480
+ if (ip === "::1" || ip === "::ffff:127.0.0.1") return true;
481
+ const hexIpv4 = normalizeIPv6MappedToIPv4(ip);
482
+ if (hexIpv4) return isPrivateIp(hexIpv4);
483
+ const v4Match = ip.match(IPV4_MAPPED_IPV6_DOTTED_PATTERN);
484
+ const num = parseIpv4(v4Match ? v4Match[1] : ip);
485
+ if (num === null) return ip.startsWith("fe80:") || ip.startsWith("fc") || ip.startsWith("fd");
486
+ return BLOCKED_PATTERNS.some((range) => num >= range.start && num <= range.end);
487
+ }
488
+ /**
489
+ * Error thrown when SSRF protection blocks a URL.
490
+ */
491
+ var SsrfError = class extends Error {
492
+ code = "SSRF_BLOCKED";
493
+ constructor(message) {
494
+ super(message);
495
+ this.name = "SsrfError";
496
+ }
497
+ };
498
+ /**
499
+ * Validate that a URL is safe to fetch (not targeting internal networks).
500
+ *
501
+ * Checks:
502
+ * 1. URL is well-formed with http/https scheme
503
+ * 2. Hostname is not a known internal name (localhost, metadata endpoints)
504
+ * 3. If hostname is an IP literal, it's not in a private range
505
+ *
506
+ * Note: DNS rebinding attacks are not fully mitigated (hostname could resolve
507
+ * to a private IP). Full protection requires resolving DNS and checking the IP
508
+ * before connecting, which needs a custom fetch implementation. This covers
509
+ * the most common SSRF vectors.
510
+ *
511
+ * @throws SsrfError if the URL targets an internal address
512
+ */
513
+ /** Maximum number of redirects to follow in ssrfSafeFetch */
514
+ const MAX_REDIRECTS = 5;
515
+ function validateExternalUrl(url) {
516
+ let parsed;
517
+ try {
518
+ parsed = new URL(url);
519
+ } catch {
520
+ throw new SsrfError("Invalid URL");
521
+ }
522
+ if (!ALLOWED_SCHEMES.has(parsed.protocol)) throw new SsrfError(`Scheme '${parsed.protocol}' is not allowed`);
523
+ const hostname = parsed.hostname.replace(IPV6_BRACKET_PATTERN, "");
524
+ if (BLOCKED_HOSTNAMES.has(hostname.toLowerCase())) throw new SsrfError("URLs targeting internal hosts are not allowed");
525
+ if (isPrivateIp(hostname)) throw new SsrfError("URLs targeting private IP addresses are not allowed");
526
+ return parsed;
527
+ }
528
+ /**
529
+ * Fetch a URL with SSRF protection on redirects.
530
+ *
531
+ * Uses `redirect: "manual"` to intercept redirects and re-validate each
532
+ * redirect target against SSRF rules before following it. This prevents
533
+ * an attacker from setting up an allowed external URL that redirects to
534
+ * an internal IP (e.g. 169.254.169.254 for cloud metadata).
535
+ *
536
+ * @throws SsrfError if the initial URL or any redirect target is internal
537
+ */
538
+ /** Headers that must be stripped when a redirect crosses origins */
539
+ const CREDENTIAL_HEADERS = [
540
+ "authorization",
541
+ "cookie",
542
+ "proxy-authorization"
543
+ ];
544
+ async function ssrfSafeFetch(url, init) {
545
+ let currentUrl = url;
546
+ let currentInit = init;
547
+ for (let i = 0; i <= MAX_REDIRECTS; i++) {
548
+ validateExternalUrl(currentUrl);
549
+ const response = await globalThis.fetch(currentUrl, {
550
+ ...currentInit,
551
+ redirect: "manual"
552
+ });
553
+ if (response.status < 300 || response.status >= 400) return response;
554
+ const location = response.headers.get("Location");
555
+ if (!location) return response;
556
+ const previousOrigin = new URL(currentUrl).origin;
557
+ currentUrl = new URL(location, currentUrl).href;
558
+ if (previousOrigin !== new URL(currentUrl).origin && currentInit) currentInit = stripCredentialHeaders(currentInit);
559
+ }
560
+ throw new SsrfError(`Too many redirects (max ${MAX_REDIRECTS})`);
561
+ }
562
+ /**
563
+ * Return a copy of init with credential headers removed.
564
+ */
565
+ function stripCredentialHeaders(init) {
566
+ if (!init.headers) return init;
567
+ const headers = new Headers(init.headers);
568
+ for (const name of CREDENTIAL_HEADERS) headers.delete(name);
569
+ return {
570
+ ...init,
571
+ headers
572
+ };
573
+ }
574
+
575
+ //#endregion
576
+ //#region src/seed/apply.ts
577
+ /**
578
+ * Seed engine - applies seed files to database
579
+ *
580
+ * This is the core implementation that bootstraps an EmDash site from a seed file.
581
+ * Apply order is critical for foreign keys and references.
582
+ */
583
+ var apply_exports = /* @__PURE__ */ __exportAll({ applySeed: () => applySeed });
584
+ const FILE_EXTENSION_PATTERN = /\.([a-z0-9]+)(?:\?|$)/i;
585
+ /** Pattern to remove file extensions */
586
+ const EXTENSION_PATTERN = /\.[^.]+$/;
587
+ /** Pattern to remove query parameters */
588
+ const QUERY_PARAM_PATTERN = /\?.*$/;
589
+ /** Pattern to remove non-alphanumeric characters (except dash and underscore) */
590
+ const SANITIZE_PATTERN = /[^a-zA-Z0-9_-]/g;
591
+ /** Pattern to collapse multiple hyphens */
592
+ const MULTIPLE_HYPHENS_PATTERN = /-+/g;
593
+ /**
594
+ * Apply a seed file to the database
595
+ *
596
+ * This function is idempotent - safe to run multiple times.
597
+ *
598
+ * @param db - Kysely database instance
599
+ * @param seed - Seed file to apply
600
+ * @param options - Application options
601
+ * @returns Result summary
602
+ */
603
+ async function applySeed(db, seed, options = {}) {
604
+ const validation = validateSeed(seed);
605
+ if (!validation.valid) throw new Error(`Invalid seed file:\n${validation.errors.join("\n")}`);
606
+ const { includeContent = false, storage, skipMediaDownload = false, onConflict = "skip" } = options;
607
+ const result = {
608
+ collections: {
609
+ created: 0,
610
+ skipped: 0,
611
+ updated: 0
612
+ },
613
+ fields: {
614
+ created: 0,
615
+ skipped: 0,
616
+ updated: 0
617
+ },
618
+ taxonomies: {
619
+ created: 0,
620
+ terms: 0
621
+ },
622
+ bylines: {
623
+ created: 0,
624
+ skipped: 0,
625
+ updated: 0
626
+ },
627
+ menus: {
628
+ created: 0,
629
+ items: 0
630
+ },
631
+ redirects: {
632
+ created: 0,
633
+ skipped: 0,
634
+ updated: 0
635
+ },
636
+ widgetAreas: {
637
+ created: 0,
638
+ widgets: 0
639
+ },
640
+ sections: {
641
+ created: 0,
642
+ skipped: 0,
643
+ updated: 0
644
+ },
645
+ settings: { applied: 0 },
646
+ content: {
647
+ created: 0,
648
+ skipped: 0,
649
+ updated: 0
650
+ },
651
+ media: {
652
+ created: 0,
653
+ skipped: 0
654
+ }
655
+ };
656
+ const mediaContext = {
657
+ db,
658
+ storage: storage ?? null,
659
+ skipMediaDownload,
660
+ mediaCache: /* @__PURE__ */ new Map()
661
+ };
662
+ const seedIdMap = /* @__PURE__ */ new Map();
663
+ const seedBylineIdMap = /* @__PURE__ */ new Map();
664
+ if (seed.settings) {
665
+ await setSiteSettings(seed.settings, db);
666
+ result.settings.applied = Object.keys(seed.settings).length;
667
+ }
668
+ if (seed.collections) {
669
+ const registry = new SchemaRegistry(db);
670
+ for (const collection of seed.collections) {
671
+ if (await registry.getCollection(collection.slug)) {
672
+ if (onConflict === "error") throw new Error(`Conflict: collection "${collection.slug}" already exists`);
673
+ if (onConflict === "update") {
674
+ await registry.updateCollection(collection.slug, {
675
+ label: collection.label,
676
+ labelSingular: collection.labelSingular,
677
+ description: collection.description,
678
+ icon: collection.icon,
679
+ supports: collection.supports || [],
680
+ urlPattern: collection.urlPattern,
681
+ commentsEnabled: collection.commentsEnabled
682
+ });
683
+ result.collections.updated++;
684
+ for (const field of collection.fields) if (await registry.getField(collection.slug, field.slug)) {
685
+ await registry.updateField(collection.slug, field.slug, {
686
+ label: field.label,
687
+ required: field.required || false,
688
+ unique: field.unique || false,
689
+ searchable: field.searchable || false,
690
+ defaultValue: field.defaultValue,
691
+ validation: field.validation,
692
+ widget: field.widget,
693
+ options: field.options
694
+ });
695
+ result.fields.updated++;
696
+ } else {
697
+ await registry.createField(collection.slug, {
698
+ slug: field.slug,
699
+ label: field.label,
700
+ type: field.type,
701
+ required: field.required || false,
702
+ unique: field.unique || false,
703
+ searchable: field.searchable || false,
704
+ defaultValue: field.defaultValue,
705
+ validation: field.validation,
706
+ widget: field.widget,
707
+ options: field.options
708
+ });
709
+ result.fields.created++;
710
+ }
711
+ continue;
712
+ }
713
+ result.collections.skipped++;
714
+ result.fields.skipped += collection.fields.length;
715
+ continue;
716
+ }
717
+ await registry.createCollection({
718
+ slug: collection.slug,
719
+ label: collection.label,
720
+ labelSingular: collection.labelSingular,
721
+ description: collection.description,
722
+ icon: collection.icon,
723
+ supports: collection.supports || [],
724
+ source: "seed",
725
+ urlPattern: collection.urlPattern,
726
+ commentsEnabled: collection.commentsEnabled
727
+ });
728
+ result.collections.created++;
729
+ for (const field of collection.fields) {
730
+ await registry.createField(collection.slug, {
731
+ slug: field.slug,
732
+ label: field.label,
733
+ type: field.type,
734
+ required: field.required || false,
735
+ unique: field.unique || false,
736
+ searchable: field.searchable || false,
737
+ defaultValue: field.defaultValue,
738
+ validation: field.validation,
739
+ widget: field.widget,
740
+ options: field.options
741
+ });
742
+ result.fields.created++;
743
+ }
744
+ }
745
+ }
746
+ if (seed.taxonomies) for (const taxonomy of seed.taxonomies) {
747
+ const existingDef = await db.selectFrom("_emdash_taxonomy_defs").selectAll().where("name", "=", taxonomy.name).executeTakeFirst();
748
+ if (existingDef) {
749
+ if (onConflict === "error") throw new Error(`Conflict: taxonomy "${taxonomy.name}" already exists`);
750
+ if (onConflict === "update") await db.updateTable("_emdash_taxonomy_defs").set({
751
+ label: taxonomy.label,
752
+ label_singular: taxonomy.labelSingular ?? null,
753
+ hierarchical: taxonomy.hierarchical ? 1 : 0,
754
+ collections: JSON.stringify(taxonomy.collections)
755
+ }).where("id", "=", existingDef.id).execute();
756
+ } else {
757
+ await db.insertInto("_emdash_taxonomy_defs").values({
758
+ id: ulid(),
759
+ name: taxonomy.name,
760
+ label: taxonomy.label,
761
+ label_singular: taxonomy.labelSingular ?? null,
762
+ hierarchical: taxonomy.hierarchical ? 1 : 0,
763
+ collections: JSON.stringify(taxonomy.collections)
764
+ }).execute();
765
+ result.taxonomies.created++;
766
+ }
767
+ if (taxonomy.terms && taxonomy.terms.length > 0) {
768
+ const termRepo = new TaxonomyRepository(db);
769
+ if (taxonomy.hierarchical) await applyHierarchicalTerms(termRepo, taxonomy.name, taxonomy.terms, result, onConflict);
770
+ else for (const term of taxonomy.terms) {
771
+ const existing = await termRepo.findBySlug(taxonomy.name, term.slug);
772
+ if (existing) {
773
+ if (onConflict === "error") throw new Error(`Conflict: taxonomy term "${term.slug}" in "${taxonomy.name}" already exists`);
774
+ if (onConflict === "update") {
775
+ await termRepo.update(existing.id, {
776
+ label: term.label,
777
+ data: term.description ? { description: term.description } : {}
778
+ });
779
+ result.taxonomies.terms++;
780
+ }
781
+ } else {
782
+ await termRepo.create({
783
+ name: taxonomy.name,
784
+ slug: term.slug,
785
+ label: term.label,
786
+ data: term.description ? { description: term.description } : void 0
787
+ });
788
+ result.taxonomies.terms++;
789
+ }
790
+ }
791
+ }
792
+ }
793
+ if (seed.bylines) {
794
+ const bylineRepo = new BylineRepository(db);
795
+ for (const byline of seed.bylines) {
796
+ const existing = await bylineRepo.findBySlug(byline.slug);
797
+ if (existing) {
798
+ if (onConflict === "error") throw new Error(`Conflict: byline "${byline.slug}" already exists`);
799
+ if (onConflict === "update") {
800
+ await bylineRepo.update(existing.id, {
801
+ displayName: byline.displayName,
802
+ bio: byline.bio ?? null,
803
+ websiteUrl: byline.websiteUrl ?? null,
804
+ isGuest: byline.isGuest
805
+ });
806
+ seedBylineIdMap.set(byline.id, existing.id);
807
+ result.bylines.updated++;
808
+ continue;
809
+ }
810
+ seedBylineIdMap.set(byline.id, existing.id);
811
+ result.bylines.skipped++;
812
+ continue;
813
+ }
814
+ const created = await bylineRepo.create({
815
+ slug: byline.slug,
816
+ displayName: byline.displayName,
817
+ bio: byline.bio ?? null,
818
+ websiteUrl: byline.websiteUrl ?? null,
819
+ isGuest: byline.isGuest
820
+ });
821
+ seedBylineIdMap.set(byline.id, created.id);
822
+ result.bylines.created++;
823
+ }
824
+ }
825
+ if (includeContent && seed.content) {
826
+ const contentRepo = new ContentRepository(db);
827
+ const bylineRepo = new BylineRepository(db);
828
+ for (const [collectionSlug, entries] of Object.entries(seed.content)) for (const entry of entries) {
829
+ const existing = await contentRepo.findBySlug(collectionSlug, entry.slug, entry.locale);
830
+ if (existing) {
831
+ if (onConflict === "error") throw new Error(`Conflict: content "${entry.slug}" in "${collectionSlug}" already exists`);
832
+ if (onConflict === "update") {
833
+ const resolvedData = await resolveReferences(entry.data, seedIdMap, mediaContext, result);
834
+ const status = entry.status || "published";
835
+ await contentRepo.update(collectionSlug, existing.id, {
836
+ status,
837
+ data: resolvedData
838
+ });
839
+ seedIdMap.set(entry.id, existing.id);
840
+ result.content.updated++;
841
+ await applyContentBylines(bylineRepo, collectionSlug, existing.id, entry, seedBylineIdMap, true);
842
+ await applyContentTaxonomies(db, collectionSlug, existing.id, entry, true);
843
+ continue;
844
+ }
845
+ result.content.skipped++;
846
+ seedIdMap.set(entry.id, existing.id);
847
+ continue;
848
+ }
849
+ const resolvedData = await resolveReferences(entry.data, seedIdMap, mediaContext, result);
850
+ let translationOf;
851
+ if (entry.translationOf) {
852
+ const sourceId = seedIdMap.get(entry.translationOf);
853
+ if (!sourceId) console.warn(`content.${collectionSlug}: translationOf "${entry.translationOf}" not found (not yet created or missing). Skipping translation link.`);
854
+ else translationOf = sourceId;
855
+ }
856
+ const status = entry.status || "published";
857
+ const created = await contentRepo.create({
858
+ type: collectionSlug,
859
+ slug: entry.slug,
860
+ status,
861
+ data: resolvedData,
862
+ locale: entry.locale,
863
+ translationOf,
864
+ publishedAt: status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : null
865
+ });
866
+ seedIdMap.set(entry.id, created.id);
867
+ result.content.created++;
868
+ await applyContentBylines(bylineRepo, collectionSlug, created.id, entry, seedBylineIdMap);
869
+ await applyContentTaxonomies(db, collectionSlug, created.id, entry, false);
870
+ }
871
+ }
872
+ if (seed.menus) for (const menu of seed.menus) {
873
+ const existingMenu = await db.selectFrom("_emdash_menus").selectAll().where("name", "=", menu.name).executeTakeFirst();
874
+ let menuId;
875
+ if (existingMenu) {
876
+ menuId = existingMenu.id;
877
+ await db.deleteFrom("_emdash_menu_items").where("menu_id", "=", menuId).execute();
878
+ } else {
879
+ menuId = ulid();
880
+ await db.insertInto("_emdash_menus").values({
881
+ id: menuId,
882
+ name: menu.name,
883
+ label: menu.label,
884
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
885
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
886
+ }).execute();
887
+ result.menus.created++;
888
+ }
889
+ const itemCount = await applyMenuItems(db, menuId, menu.items, null, 0, seedIdMap);
890
+ result.menus.items += itemCount;
891
+ }
892
+ if (seed.redirects) {
893
+ const redirectRepo = new RedirectRepository(db);
894
+ for (const redirect of seed.redirects) {
895
+ const existing = await redirectRepo.findBySource(redirect.source);
896
+ if (existing) {
897
+ if (onConflict === "error") throw new Error(`Conflict: redirect "${redirect.source}" already exists`);
898
+ if (onConflict === "update") {
899
+ await redirectRepo.update(existing.id, {
900
+ destination: redirect.destination,
901
+ type: redirect.type,
902
+ enabled: redirect.enabled,
903
+ groupName: redirect.groupName
904
+ });
905
+ result.redirects.updated++;
906
+ continue;
907
+ }
908
+ result.redirects.skipped++;
909
+ continue;
910
+ }
911
+ await redirectRepo.create({
912
+ source: redirect.source,
913
+ destination: redirect.destination,
914
+ type: redirect.type,
915
+ enabled: redirect.enabled,
916
+ groupName: redirect.groupName
917
+ });
918
+ result.redirects.created++;
919
+ }
920
+ }
921
+ if (seed.widgetAreas) for (const area of seed.widgetAreas) {
922
+ const existingArea = await db.selectFrom("_emdash_widget_areas").selectAll().where("name", "=", area.name).executeTakeFirst();
923
+ let areaId;
924
+ if (existingArea) {
925
+ areaId = existingArea.id;
926
+ await db.deleteFrom("_emdash_widgets").where("area_id", "=", areaId).execute();
927
+ } else {
928
+ areaId = ulid();
929
+ await db.insertInto("_emdash_widget_areas").values({
930
+ id: areaId,
931
+ name: area.name,
932
+ label: area.label,
933
+ description: area.description ?? null
934
+ }).execute();
935
+ result.widgetAreas.created++;
936
+ }
937
+ for (let i = 0; i < area.widgets.length; i++) {
938
+ const widget = area.widgets[i];
939
+ await applyWidget(db, areaId, widget, i);
940
+ result.widgetAreas.widgets++;
941
+ }
942
+ }
943
+ if (seed.sections) for (const section of seed.sections) {
944
+ const existing = await db.selectFrom("_emdash_sections").select("id").where("slug", "=", section.slug).executeTakeFirst();
945
+ if (existing) {
946
+ if (onConflict === "error") throw new Error(`Conflict: section "${section.slug}" already exists`);
947
+ if (onConflict === "update") {
948
+ await db.updateTable("_emdash_sections").set({
949
+ title: section.title,
950
+ description: section.description ?? null,
951
+ keywords: section.keywords ? JSON.stringify(section.keywords) : null,
952
+ content: JSON.stringify(section.content),
953
+ source: section.source || "theme",
954
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
955
+ }).where("id", "=", existing.id).execute();
956
+ result.sections.updated++;
957
+ continue;
958
+ }
959
+ result.sections.skipped++;
960
+ continue;
961
+ }
962
+ const id = ulid();
963
+ const now = (/* @__PURE__ */ new Date()).toISOString();
964
+ await db.insertInto("_emdash_sections").values({
965
+ id,
966
+ slug: section.slug,
967
+ title: section.title,
968
+ description: section.description ?? null,
969
+ keywords: section.keywords ? JSON.stringify(section.keywords) : null,
970
+ content: JSON.stringify(section.content),
971
+ preview_media_id: null,
972
+ source: section.source || "theme",
973
+ theme_id: section.source === "theme" ? section.slug : null,
974
+ created_at: now,
975
+ updated_at: now
976
+ }).execute();
977
+ result.sections.created++;
978
+ }
979
+ if (seed.collections) {
980
+ const ftsManager = new FTSManager(db);
981
+ for (const collection of seed.collections) if (collection.supports?.includes("search")) {
982
+ if ((await ftsManager.getSearchableFields(collection.slug)).length > 0) try {
983
+ await ftsManager.enableSearch(collection.slug);
984
+ } catch (err) {
985
+ console.warn(`Failed to enable search for ${collection.slug}:`, err);
986
+ }
987
+ }
988
+ }
989
+ return result;
990
+ }
991
+ /**
992
+ * Apply hierarchical taxonomy terms (parents before children)
993
+ */
994
+ async function applyHierarchicalTerms(termRepo, taxonomyName, terms, result, onConflict = "skip") {
995
+ const slugToId = /* @__PURE__ */ new Map();
996
+ let remaining = [...terms];
997
+ let maxPasses = 10;
998
+ while (remaining.length > 0 && maxPasses > 0) {
999
+ const processedThisPass = [];
1000
+ for (const term of remaining) if (!term.parent || slugToId.has(term.parent)) {
1001
+ const parentId = term.parent ? slugToId.get(term.parent) : void 0;
1002
+ const existing = await termRepo.findBySlug(taxonomyName, term.slug);
1003
+ if (existing) {
1004
+ if (onConflict === "error") throw new Error(`Conflict: taxonomy term "${term.slug}" in "${taxonomyName}" already exists`);
1005
+ if (onConflict === "update") {
1006
+ await termRepo.update(existing.id, {
1007
+ label: term.label,
1008
+ parentId,
1009
+ data: term.description ? { description: term.description } : {}
1010
+ });
1011
+ result.taxonomies.terms++;
1012
+ }
1013
+ slugToId.set(term.slug, existing.id);
1014
+ } else {
1015
+ const created = await termRepo.create({
1016
+ name: taxonomyName,
1017
+ slug: term.slug,
1018
+ label: term.label,
1019
+ parentId,
1020
+ data: term.description ? { description: term.description } : void 0
1021
+ });
1022
+ slugToId.set(term.slug, created.id);
1023
+ result.taxonomies.terms++;
1024
+ }
1025
+ processedThisPass.push(term.slug);
1026
+ }
1027
+ remaining = remaining.filter((t) => !processedThisPass.includes(t.slug));
1028
+ maxPasses--;
1029
+ }
1030
+ if (remaining.length > 0) console.warn(`Could not process ${remaining.length} terms due to missing parents`);
1031
+ }
1032
+ /**
1033
+ * Apply byline credits to a content entry.
1034
+ * In update mode, clears existing credits even if the seed has none.
1035
+ */
1036
+ async function applyContentBylines(bylineRepo, collectionSlug, contentId, entry, seedBylineIdMap, isUpdate = false) {
1037
+ if (!entry.bylines || entry.bylines.length === 0) {
1038
+ if (isUpdate) await bylineRepo.setContentBylines(collectionSlug, contentId, []);
1039
+ return;
1040
+ }
1041
+ const credits = entry.bylines.map((credit) => {
1042
+ const bylineId = seedBylineIdMap.get(credit.byline);
1043
+ if (!bylineId) return null;
1044
+ return {
1045
+ bylineId,
1046
+ roleLabel: credit.roleLabel ?? null
1047
+ };
1048
+ }).filter((credit) => Boolean(credit));
1049
+ if (credits.length !== entry.bylines.length) console.warn(`content.${collectionSlug}.${entry.slug}: one or more byline refs could not be resolved`);
1050
+ if (credits.length > 0 || isUpdate) await bylineRepo.setContentBylines(collectionSlug, contentId, credits);
1051
+ }
1052
+ /**
1053
+ * Apply taxonomy term assignments to a content entry.
1054
+ * In update mode, clears existing assignments before re-attaching.
1055
+ */
1056
+ async function applyContentTaxonomies(db, collectionSlug, contentId, entry, isUpdate) {
1057
+ if (isUpdate) await db.deleteFrom("content_taxonomies").where("collection", "=", collectionSlug).where("entry_id", "=", contentId).execute();
1058
+ if (!entry.taxonomies) return;
1059
+ for (const [taxonomyName, termSlugs] of Object.entries(entry.taxonomies)) {
1060
+ const termRepo = new TaxonomyRepository(db);
1061
+ for (const termSlug of termSlugs) {
1062
+ const term = await termRepo.findBySlug(taxonomyName, termSlug);
1063
+ if (term) await termRepo.attachToEntry(collectionSlug, contentId, term.id);
1064
+ }
1065
+ }
1066
+ }
1067
+ /**
1068
+ * Apply menu items recursively
1069
+ */
1070
+ async function applyMenuItems(db, menuId, items, parentId, startOrder, seedIdMap) {
1071
+ let count = 0;
1072
+ let order = startOrder;
1073
+ for (const item of items) {
1074
+ const itemId = ulid();
1075
+ let referenceId = null;
1076
+ let referenceCollection = null;
1077
+ if (item.type === "page" || item.type === "post") {
1078
+ if (item.ref && seedIdMap.has(item.ref)) {
1079
+ referenceId = seedIdMap.get(item.ref);
1080
+ referenceCollection = item.collection || `${item.type}s`;
1081
+ }
1082
+ }
1083
+ await db.insertInto("_emdash_menu_items").values({
1084
+ id: itemId,
1085
+ menu_id: menuId,
1086
+ parent_id: parentId,
1087
+ sort_order: order,
1088
+ type: item.type,
1089
+ reference_collection: referenceCollection,
1090
+ reference_id: referenceId,
1091
+ custom_url: item.url ?? null,
1092
+ label: item.label || "",
1093
+ title_attr: item.titleAttr ?? null,
1094
+ target: item.target ?? null,
1095
+ css_classes: item.cssClasses ?? null,
1096
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1097
+ }).execute();
1098
+ count++;
1099
+ order++;
1100
+ if (item.children && item.children.length > 0) {
1101
+ const childCount = await applyMenuItems(db, menuId, item.children, itemId, 0, seedIdMap);
1102
+ count += childCount;
1103
+ }
1104
+ }
1105
+ return count;
1106
+ }
1107
+ /**
1108
+ * Apply a widget
1109
+ */
1110
+ async function applyWidget(db, areaId, widget, sortOrder) {
1111
+ await db.insertInto("_emdash_widgets").values({
1112
+ id: ulid(),
1113
+ area_id: areaId,
1114
+ sort_order: sortOrder,
1115
+ type: widget.type,
1116
+ title: widget.title ?? null,
1117
+ content: widget.content ? JSON.stringify(widget.content) : null,
1118
+ menu_name: widget.menuName ?? null,
1119
+ component_id: widget.componentId ?? null,
1120
+ component_props: widget.props ? JSON.stringify(widget.props) : null
1121
+ }).execute();
1122
+ }
1123
+ /**
1124
+ * Type guard for $media reference
1125
+ */
1126
+ function isSeedMediaReference(value) {
1127
+ if (typeof value !== "object" || value === null || !("$media" in value)) return false;
1128
+ const media = value.$media;
1129
+ return typeof media === "object" && media !== null && "url" in media && typeof media.url === "string";
1130
+ }
1131
+ /**
1132
+ * Resolve $ref: and $media references in content data
1133
+ */
1134
+ async function resolveReferences(data, seedIdMap, mediaContext, result) {
1135
+ const resolved = {};
1136
+ for (const [key, value] of Object.entries(data)) resolved[key] = await resolveValue(value, seedIdMap, mediaContext, result);
1137
+ return resolved;
1138
+ }
1139
+ /**
1140
+ * Resolve a single value recursively
1141
+ */
1142
+ async function resolveValue(value, seedIdMap, mediaContext, result) {
1143
+ if (typeof value === "string" && value.startsWith("$ref:")) {
1144
+ const seedId = value.slice(5);
1145
+ return seedIdMap.get(seedId) ?? value;
1146
+ }
1147
+ if (isSeedMediaReference(value)) return resolveMedia(value, mediaContext, result);
1148
+ if (Array.isArray(value)) return Promise.all(value.map((item) => resolveValue(item, seedIdMap, mediaContext, result)));
1149
+ if (typeof value === "object" && value !== null) {
1150
+ const resolved = {};
1151
+ for (const [k, v] of Object.entries(value)) resolved[k] = await resolveValue(v, seedIdMap, mediaContext, result);
1152
+ return resolved;
1153
+ }
1154
+ return value;
1155
+ }
1156
+ /**
1157
+ * Resolve a $media reference by downloading and uploading the media
1158
+ */
1159
+ async function resolveMedia(ref, ctx, result) {
1160
+ const { url, alt, filename, caption } = ref.$media;
1161
+ const cached = ctx.mediaCache.get(url);
1162
+ if (cached) {
1163
+ result.media.skipped++;
1164
+ return {
1165
+ ...cached,
1166
+ alt: alt ?? cached.alt
1167
+ };
1168
+ }
1169
+ if (ctx.skipMediaDownload) {
1170
+ const mediaValue = {
1171
+ provider: "external",
1172
+ id: ulid(),
1173
+ src: url,
1174
+ alt: alt ?? void 0,
1175
+ filename: filename ?? void 0
1176
+ };
1177
+ ctx.mediaCache.set(url, mediaValue);
1178
+ result.media.created++;
1179
+ return mediaValue;
1180
+ }
1181
+ if (!ctx.storage) {
1182
+ console.warn(`Skipping $media reference (no storage configured): ${url}`);
1183
+ result.media.skipped++;
1184
+ return null;
1185
+ }
1186
+ try {
1187
+ validateExternalUrl(url);
1188
+ console.log(` 📥 Downloading: ${url}`);
1189
+ const response = await ssrfSafeFetch(url, { headers: { "User-Agent": "EmDash-CMS/1.0" } });
1190
+ if (!response.ok) {
1191
+ console.warn(` ⚠️ Failed to download ${url}: ${response.status}`);
1192
+ result.media.skipped++;
1193
+ return null;
1194
+ }
1195
+ const contentType = response.headers.get("content-type") || "application/octet-stream";
1196
+ const ext = getExtensionFromContentType(contentType) || getExtensionFromUrl(url) || ".bin";
1197
+ const id = ulid();
1198
+ const finalFilename = filename || generateFilename(url, ext);
1199
+ const storageKey = `${id}${ext}`;
1200
+ const arrayBuffer = await response.arrayBuffer();
1201
+ const body = new Uint8Array(arrayBuffer);
1202
+ let width;
1203
+ let height;
1204
+ if (contentType.startsWith("image/")) {
1205
+ const dimensions = getImageDimensions(body);
1206
+ width = dimensions?.width;
1207
+ height = dimensions?.height;
1208
+ }
1209
+ await ctx.storage.upload({
1210
+ key: storageKey,
1211
+ body,
1212
+ contentType
1213
+ });
1214
+ await new MediaRepository(ctx.db).create({
1215
+ filename: finalFilename,
1216
+ mimeType: contentType,
1217
+ size: body.length,
1218
+ width,
1219
+ height,
1220
+ alt,
1221
+ caption,
1222
+ storageKey,
1223
+ status: "ready"
1224
+ });
1225
+ const mediaValue = {
1226
+ provider: "local",
1227
+ id,
1228
+ alt: alt ?? void 0,
1229
+ width,
1230
+ height,
1231
+ mimeType: contentType,
1232
+ filename: finalFilename,
1233
+ meta: { storageKey }
1234
+ };
1235
+ ctx.mediaCache.set(url, mediaValue);
1236
+ result.media.created++;
1237
+ console.log(` ✅ Uploaded: ${finalFilename}`);
1238
+ return mediaValue;
1239
+ } catch (error) {
1240
+ console.warn(` ⚠️ Error processing $media ${url}:`, error instanceof Error ? error.message : error);
1241
+ result.media.skipped++;
1242
+ return null;
1243
+ }
1244
+ }
1245
+ /**
1246
+ * Get file extension from content type
1247
+ */
1248
+ function getExtensionFromContentType(contentType) {
1249
+ const baseMime = contentType.split(";")[0].trim();
1250
+ const ext = mime.getExtension(baseMime);
1251
+ return ext ? `.${ext}` : null;
1252
+ }
1253
+ /**
1254
+ * Get file extension from URL
1255
+ */
1256
+ function getExtensionFromUrl(url) {
1257
+ try {
1258
+ const match = new URL(url).pathname.match(FILE_EXTENSION_PATTERN);
1259
+ return match ? `.${match[1]}` : null;
1260
+ } catch {
1261
+ return null;
1262
+ }
1263
+ }
1264
+ /**
1265
+ * Generate a filename from URL
1266
+ */
1267
+ function generateFilename(url, ext) {
1268
+ try {
1269
+ return `${(new URL(url).pathname.split("/").pop() || "media").replace(EXTENSION_PATTERN, "").replace(QUERY_PARAM_PATTERN, "").replace(SANITIZE_PATTERN, "-").replace(MULTIPLE_HYPHENS_PATTERN, "-") || "media"}${ext}`;
1270
+ } catch {
1271
+ return `media${ext}`;
1272
+ }
1273
+ }
1274
+ /**
1275
+ * Get image dimensions from buffer using image-size.
1276
+ * Supports PNG, JPEG, GIF, WebP, AVIF, SVG, TIFF, and more.
1277
+ */
1278
+ function getImageDimensions(buffer) {
1279
+ try {
1280
+ const result = imageSize(buffer);
1281
+ if (result.width != null && result.height != null) return {
1282
+ width: result.width,
1283
+ height: result.height
1284
+ };
1285
+ return null;
1286
+ } catch {
1287
+ return null;
1288
+ }
1289
+ }
1290
+
1291
+ //#endregion
1292
+ export { stripCredentialHeaders as a, getSiteSettings as c, TaxonomyRepository as d, ssrfSafeFetch as i, setSiteSettings as l, apply_exports as n, validateExternalUrl as o, SsrfError as r, getSiteSetting as s, applySeed as t, OptionsRepository as u };
1293
+ //# sourceMappingURL=apply-Bjfq_b4-.mjs.map