ghost 6.55.0 → 6.57.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 (483) hide show
  1. package/bin/validate-adapters.js +74 -0
  2. package/bin/validate-adapters.ts +87 -0
  3. package/bin/warm-compile-cache.sh +60 -0
  4. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  5. package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
  6. package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
  7. package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
  8. package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
  9. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  10. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  11. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  12. package/core/app.js +1 -1
  13. package/core/boot.js +21 -25
  14. package/core/bridge.js +10 -12
  15. package/core/built/admin/assets/Combination-BDvCdg1t.js +41 -0
  16. package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
  17. package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
  18. package/core/built/admin/assets/about-C7O-AgRZ.js +1 -0
  19. package/core/built/admin/assets/{account-migration-sjHLx5dy.js → account-migration-Cv_ol1ko.js} +1 -1
  20. package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
  21. package/core/built/admin/assets/activitypub/{account-migration-DoYXtnZ8.js → account-migration-DHFbePl6.js} +7 -7
  22. package/core/built/admin/assets/activitypub/activitypub.js +6 -6
  23. package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-CT4tS2_S.js} +2 -2
  24. package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-ByYWKipo.js} +2 -2
  25. package/core/built/admin/assets/activitypub/{bluesky-sharing-Bxeh6MKW.js → bluesky-sharing-srVnSHDz.js} +16 -16
  26. package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-C2sos5Ht.js} +3 -3
  27. package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-B1UBTOoZ.js} +2 -2
  28. package/core/built/admin/assets/activitypub/{dist-D8sFfdpC.js → dist-MPsb2WZ1.js} +12 -12
  29. package/core/built/admin/assets/activitypub/{domain-CWueFW6q.js → domain-DzcM9Dzo.js} +9 -9
  30. package/core/built/admin/assets/activitypub/{edit-profile-BGxPU7N5.js → edit-profile-CsQ_4xAk.js} +945 -945
  31. package/core/built/admin/assets/activitypub/{explore-DBK-u0Ch.js → explore-CJe0DZBK.js} +8 -8
  32. package/core/built/admin/assets/activitypub/{feed-Bcir5RQZ.js → feed-CKV3qWON.js} +33 -33
  33. package/core/built/admin/assets/activitypub/{field-BV-CIT3d.js → field-DckHfDJf.js} +3 -3
  34. package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-Dlp8RM55.js} +2 -2
  35. package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-CsPy4R0M.js} +2 -2
  36. package/core/built/admin/assets/activitypub/{inbox-yC0nfvzl.js → inbox-DAsjzUUe.js} +85 -85
  37. package/core/built/admin/assets/activitypub/{label-DTRS9TDk.js → label-DWyo9E5T.js} +3 -3
  38. package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +1 -0
  39. package/core/built/admin/assets/activitypub/{moderation-Dwl9t0pb.js → moderation-B0Inc03M.js} +11 -11
  40. package/core/built/admin/assets/activitypub/{note-B-zKsZ7r.js → note-CSE54lER.js} +7 -7
  41. package/core/built/admin/assets/activitypub/{notifications-DjJZD54t.js → notifications-RSwbKqdh.js} +9 -9
  42. package/core/built/admin/assets/activitypub/{preferences-CoU8vs6d.js → preferences-CE231n2_.js} +20 -20
  43. package/core/built/admin/assets/activitypub/{profile-BAsaJekx.js → profile-aaQ1j1Eo.js} +237 -237
  44. package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cjb0Vs3m.js} +2 -2
  45. package/core/built/admin/assets/activitypub/{routes-CLH3OVTP.js → routes-Djb0mc9k.js} +1139 -1150
  46. package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-D8pROI5n.js} +3 -3
  47. package/core/built/admin/assets/activitypub/{settings-DA6pNJ-Q.js → settings-DMc32IQl.js} +13 -13
  48. package/core/built/admin/assets/activitypub/{site-CQD42Pw5.js → site-DVIcsohX.js} +81 -81
  49. package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-9Yey9usF.js} +5 -5
  50. package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DdYmKRXV.js} +6 -6
  51. package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-hdSkcjZU.js} +10 -10
  52. package/core/built/admin/assets/activitypub/{tabs-Cg0saow1.js → tabs-CUqseYzQ.js} +317 -315
  53. package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-Coni6OXM.js} +3 -3
  54. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
  55. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-B046AM38.js} +115 -96
  56. package/core/built/admin/assets/activitypub/{use-reply-chain-data-Br4aOWOh.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
  57. package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-BRfRYd16.js} +2 -2
  58. package/core/built/admin/assets/add-integration-modal-m9Dv6f77.js +1 -0
  59. package/core/built/admin/assets/add-newsletter-modal-CPSVZL2H.js +1 -0
  60. package/core/built/admin/assets/add-recommendation-modal-Ci9Z5yat.js +1 -0
  61. package/core/built/admin/assets/analytics-DqSCVV8U.js +1 -0
  62. package/core/built/admin/assets/announcement-bar-modal-DU3avV7d.js +1 -0
  63. package/core/built/admin/assets/api-keys-BK0LylyK.js +1 -0
  64. package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
  65. package/core/built/admin/assets/{audience-DjEWNhp3.js → audience-DTJtXXWY.js} +1 -1
  66. package/core/built/admin/assets/automated-emails-PayPCT8L.js +1 -0
  67. package/core/built/admin/assets/{automations-DhKeS_U5.js → automations-162q5HjL.js} +1 -1
  68. package/core/built/admin/assets/{automations-Bx3VcFIL.js → automations-dtQ_MRZ7.js} +1 -1
  69. package/core/built/admin/assets/{bluesky-sharing-ClMd0-Ft.js → bluesky-sharing-B8Es3AuV.js} +1 -1
  70. package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
  71. package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
  72. package/core/built/admin/assets/{breadcrumb-BD0U2ygg.js → breadcrumb-tg5zL--z.js} +1 -1
  73. package/core/built/admin/assets/build-CsiH26uS.js +1 -0
  74. package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
  75. package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
  76. package/core/built/admin/assets/{chart-helpers-fjQKRVyY.js → chart-helpers-CZElTGYL.js} +1 -1
  77. package/core/built/admin/assets/checkbox-Cyo-x7mC.js +1 -0
  78. package/core/built/admin/assets/chunk.524.cd5498dd4e195f679266.js +25 -0
  79. package/core/built/admin/assets/chunk.582.6f92ec1f03734bf2ea3f.js +27 -0
  80. package/core/built/admin/assets/{chunk.481.6c7395a49484bb4583aa.js → chunk.873.b8fc05a8ab877fb819ed.js} +62 -60
  81. package/core/built/admin/assets/code-editor-view-Dq1rQ4tS.js +1 -0
  82. package/core/built/admin/assets/color-picker-field-BonBgxhU.js +1 -0
  83. package/core/built/admin/assets/color-utils-BsrwXuDz.js +1 -0
  84. package/core/built/admin/assets/combobox-uX5r_paI.js +1 -0
  85. package/core/built/admin/assets/command-DxwBO2pF.js +1 -0
  86. package/core/built/admin/assets/comments-Dn-RO-nU.js +1 -0
  87. package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
  88. package/core/built/admin/assets/confirmation-modal-B6D6mk6R.js +1 -0
  89. package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
  90. package/core/built/admin/assets/content-api-modal-BxQ-7T6u.js +1 -0
  91. package/core/built/admin/assets/copy-field-BAxL4Zqn.js +1 -0
  92. package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
  93. package/core/built/admin/assets/custom-integration-modal---d_l7lc.js +1 -0
  94. package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
  95. package/core/built/admin/assets/{date-range-select-BFp58EMk.js → date-range-select-PB2n1HZf.js} +1 -1
  96. package/core/built/admin/assets/debounce-Dg2fW5Rg.js +3 -0
  97. package/core/built/admin/assets/design-and-theme-modal-DYRT207a.js +23 -0
  98. package/core/built/admin/assets/dirty-confirm-dialog-BnDJ39ao.js +1 -0
  99. package/core/built/admin/assets/disabled-sources-indicator-iMHrkyV3.js +1 -0
  100. package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
  101. package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
  102. package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS02.js} +1 -1
  103. package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-D5vhMdXf.js} +1 -1
  104. package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
  105. package/core/built/admin/assets/dist-WeOIvSYM.js +23 -0
  106. package/core/built/admin/assets/{dist-DyViJwIy.js → dist-cFzKLG7y.js} +3 -3
  107. package/core/built/admin/assets/dist-j1xxxnY8.js +1 -0
  108. package/core/built/admin/assets/dist-myQaY0ka.js +1 -0
  109. package/core/built/admin/assets/domain-BR8FIdjD.js +1 -0
  110. package/core/built/admin/assets/{dropzone-DlK-K2DQ.js → dropzone-hNxmUvwD.js} +1 -1
  111. package/core/built/admin/assets/{edit-profile-BKT-REJw.js → edit-profile-CENrkY0l.js} +1 -1
  112. package/core/built/admin/assets/editor-B18D0lm8.js +8 -0
  113. package/core/built/admin/assets/embed-signup-form-modal-BsCYUMvQ.js +9 -0
  114. package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
  115. package/core/built/admin/assets/{explore-ClUpJZOp.js → explore-BmrnljWw.js} +1 -1
  116. package/core/built/admin/assets/{feed-DF92dUMP.js → feed-Dy4li6i8.js} +1 -1
  117. package/core/built/admin/assets/{field-Do7lieBA.js → field-Dh_lwk13.js} +1 -1
  118. package/core/built/admin/assets/filter-sources-D2zDuJH5.js +1 -0
  119. package/core/built/admin/assets/filters-3JBQcNtJ.js +1 -0
  120. package/core/built/admin/assets/filters-CFt_BDe4.js +1 -0
  121. package/core/built/admin/assets/first-promoter-modal-D6Rjxugw.js +1 -0
  122. package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
  123. package/core/built/admin/assets/{ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
  124. package/core/built/admin/assets/{ghost-c4057474943effc568c5659ccc4909d7.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +420 -398
  125. package/core/built/admin/assets/{ghost-dark-db36d795d21c61426837df40af270966.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
  126. package/core/built/admin/assets/{gift-link-modal-f9VIMNDv.js → gift-link-modal-zMtlJhLQ.js} +1 -1
  127. package/core/built/admin/assets/{gift-links-CsUhefWG.js → gift-links-CVuqLhsZ.js} +1 -1
  128. package/core/built/admin/assets/global-data-provider-B949LDXJ.js +1 -0
  129. package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
  130. package/core/built/admin/assets/{growth-CVw1My4S.js → growth-C38Xtp8D.js} +2 -2
  131. package/core/built/admin/assets/{growth-B3I6gNLm.js → growth-CFws67_i.js} +1 -1
  132. package/core/built/admin/assets/history-modal-ILAOkbxH.js +1 -0
  133. package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
  134. package/core/built/admin/assets/html-field-9a9Id_2q.js +1 -0
  135. package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
  136. package/core/built/admin/assets/images-CrQKVc9Q.js +1 -0
  137. package/core/built/admin/assets/{inbox-CFBSu4Ed.js → inbox-BrSRzyvz.js} +6 -6
  138. package/core/built/admin/assets/index-BHy2DZX-.js +67 -0
  139. package/core/built/admin/assets/index-Ds2iUoF6.css +1 -0
  140. package/core/built/admin/assets/index.es-B9tvf-t4.js +1 -0
  141. package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
  142. package/core/built/admin/assets/input-group-6vEPvX9S.js +1 -0
  143. package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
  144. package/core/built/admin/assets/integration-header-B4DyXV06.js +1 -0
  145. package/core/built/admin/assets/integrations-BT6_iJvu.js +1 -0
  146. package/core/built/admin/assets/invite-user-modal-5xDCEw90.js +1 -0
  147. package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
  148. package/core/built/admin/assets/koenig-editor-base-BWLEB9Vf.js +1 -0
  149. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9535 -9527
  150. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
  151. package/core/built/admin/assets/{koenig-lexical-f8vHRYvz.js → koenig-lexical-Cj62r_y9.js} +86 -86
  152. package/core/built/admin/assets/{kpi-card-DSap9rAy.js → kpi-card-DOUnkZcN.js} +1 -1
  153. package/core/built/admin/assets/{kpi-card-DU0rn9Cj.js → kpi-card-IHDo8Q56.js} +1 -1
  154. package/core/built/admin/assets/{kpi-helpers-Dz33AV6h.js → kpi-helpers-CjUdECAK.js} +1 -1
  155. package/core/built/admin/assets/{kpi-tabs-BtTTrKvJ.js → kpi-tabs-X3DkOYbI.js} +1 -1
  156. package/core/built/admin/assets/{label-DS8Mi6G5.js → label-Dq6QaOuS.js} +1 -1
  157. package/core/built/admin/assets/label-picker-8YlhS2U3.js +1 -0
  158. package/core/built/admin/assets/labels-DAPjKPy2.js +1 -0
  159. package/core/built/admin/assets/layout-TMj4P-vv.js +1 -0
  160. package/core/built/admin/assets/limit-modal-BRPsit_B.js +1 -0
  161. package/core/built/admin/assets/link-helpers-Bzkk68GD.js +1 -0
  162. package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
  163. package/core/built/admin/assets/member-custom-fields-CmvsuuV_.js +1 -0
  164. package/core/built/admin/assets/member-detail-DCvRrkmJ.js +1 -0
  165. package/core/built/admin/assets/members-DxbSKngD.js +11 -0
  166. package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
  167. package/core/built/admin/assets/{moderation-DySbgyUS.js → moderation-Da5lhhWh.js} +1 -1
  168. package/core/built/admin/assets/multi-select-combobox-CYdkH7KQ.js +1 -0
  169. package/core/built/admin/assets/navigation-modal-BIoTwtMy.js +1 -0
  170. package/core/built/admin/assets/newsletter-BptlVoJm.js +1 -0
  171. package/core/built/admin/assets/newsletter-detail-modal-BIvxqE0N.js +1 -0
  172. package/core/built/admin/assets/newsletter-emails-Bf8IZLsP.js +1 -0
  173. package/core/built/admin/assets/newsletters-BgrIZk0V.js +1 -0
  174. package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
  175. package/core/built/admin/assets/{note-C8kxrz6w.js → note-DYcLOmDb.js} +1 -1
  176. package/core/built/admin/assets/{notifications-CgtSfX7t.js → notifications-B2SPFIV9.js} +1 -1
  177. package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
  178. package/core/built/admin/assets/offers-container-modal-BZnZITMw.js +1 -0
  179. package/core/built/admin/assets/{onboarding-route-4qtziA9d.js → onboarding-route-DgdfxkQh.js} +1 -1
  180. package/core/built/admin/assets/overview-BYQJXSog.js +1 -0
  181. package/core/built/admin/assets/overview-Dt2_mR6U.js +1 -0
  182. package/core/built/admin/assets/{page-header-B3Wb9Jqv.js → page-header-CpmxJj5B.js} +1 -1
  183. package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
  184. package/core/built/admin/assets/pintura-modal-B4MuBuuO.js +1 -0
  185. package/core/built/admin/assets/portal-frame-BF0NBIji.js +1 -0
  186. package/core/built/admin/assets/portal-modal-DMVbm0oy.js +1 -0
  187. package/core/built/admin/assets/{post-analytics-We-ko5Cb.js → post-analytics-CXWBOGyP.js} +1 -1
  188. package/core/built/admin/assets/post-analytics-header-ChzjXVPt.js +1 -0
  189. package/core/built/admin/assets/{post-analytics-provider-B6UynTGb.js → post-analytics-provider-Cgw4U_fw.js} +1 -1
  190. package/core/built/admin/assets/{post-share-modal-DFfO93_0.js → post-share-modal-B9Vmk_XM.js} +1 -1
  191. package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
  192. package/core/built/admin/assets/preferences-BfzJmnsd.js +5 -0
  193. package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
  194. package/core/built/admin/assets/preview-modal-Dw_HtD6S.js +1 -0
  195. package/core/built/admin/assets/profile-BT5vtv9b.js +1 -0
  196. package/core/built/admin/assets/radio-group-I7ou7PoJ.js +1 -0
  197. package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
  198. package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-DSQfprxH.js} +1 -1
  199. package/core/built/admin/assets/select-D9oU1IoM.js +1 -0
  200. package/core/built/admin/assets/settings-CfZFbyu_.js +4 -0
  201. package/core/built/admin/assets/{settings-WXNeWyU1.js → settings-CpvHWix7.js} +1 -1
  202. package/core/built/admin/assets/settings-breadcrumbs-K0USP2nw.js +1 -0
  203. package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
  204. package/core/built/admin/assets/{share-modal-DsgW3wbg.js → share-modal-ClIyPzc5.js} +1 -1
  205. package/core/built/admin/assets/slack-modal-1XXqOfiw.js +1 -0
  206. package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
  207. package/core/built/admin/assets/{sort-button-Bb_FCl7e.js → sort-button-CdBsm40v.js} +1 -1
  208. package/core/built/admin/assets/{source-icon-BeaRxUn9.js → source-icon-CEYmVLXR.js} +1 -1
  209. package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
  210. package/core/built/admin/assets/{stats-Bjj_kmJ1.js → stats-D8nZCTy5.js} +1 -1
  211. package/core/built/admin/assets/{stats-view-BxPbW6K1.js → stats-view-CugFVf0g.js} +1 -1
  212. package/core/built/admin/assets/{step-1-B1e3-1F8.js → step-1-DOYjrORn.js} +1 -1
  213. package/core/built/admin/assets/{step-2-C5WaeBJo.js → step-2-vA6RsarH.js} +1 -1
  214. package/core/built/admin/assets/{step-3-B46iVPwu.js → step-3-BtIBxMId.js} +2 -2
  215. package/core/built/admin/assets/stripe-button-UFP4QRXd.js +1 -0
  216. package/core/built/admin/assets/stripe-connect-modal-C_L3uM6n.js +1 -0
  217. package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
  218. package/core/built/admin/assets/{table-CB-4_HBJ.js → table-TMCgCQK7.js} +1 -1
  219. package/core/built/admin/assets/tabs-Dd6oOKbv.js +1 -0
  220. package/core/built/admin/assets/tag-detail-D3l-UJPR.js +1 -0
  221. package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
  222. package/core/built/admin/assets/tags-WdKQGWR8.js +1 -0
  223. package/core/built/admin/assets/testimonials-modal-CqWp-xXJ.js +1 -0
  224. package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
  225. package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
  226. package/core/built/admin/assets/tier-detail-modal-BkcR2u0A.js +1 -0
  227. package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
  228. package/core/built/admin/assets/toggle-group-Guorw93z.js +1 -0
  229. package/core/built/admin/assets/{topic-filter-DAFT87tQ.js → topic-filter-CbgoWIHn.js} +1 -1
  230. package/core/built/admin/assets/transistor-modal-DuNnnpal.js +1 -0
  231. package/core/built/admin/assets/unsplash-modal-y2bVwTHH.js +1 -0
  232. package/core/built/admin/assets/{use-analytics-data-S0FTd43s.js → use-analytics-data-B-b4wNSC.js} +1 -1
  233. package/core/built/admin/assets/use-check-theme-limit-error-m09J38Mq.js +2 -0
  234. package/core/built/admin/assets/use-feature-flag-ud0Qz8vt.js +1 -0
  235. package/core/built/admin/assets/{use-filter-params-Gpoq2E1L.js → use-filter-params-BnbBDc7T.js} +1 -1
  236. package/core/built/admin/assets/use-filterable-api-iMpDZBxD.js +1 -0
  237. package/core/built/admin/assets/use-form-vRjI0Kwk.js +1 -0
  238. package/core/built/admin/assets/{use-growth-stats-CIaiHOVb.js → use-growth-stats-DPU5rt3W.js} +1 -1
  239. package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
  240. package/core/built/admin/assets/use-limiter-BkN3E4gw.js +2 -0
  241. package/core/built/admin/assets/use-pintura-config-C_z8HTG2.js +1 -0
  242. package/core/built/admin/assets/use-pintura-editor-DNgbkhGp.js +1 -0
  243. package/core/built/admin/assets/{use-post-referrers-Cq2vBPrA.js → use-post-referrers-C2mXyrp-.js} +1 -1
  244. package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
  245. package/core/built/admin/assets/{use-reply-chain-data-DBDRtGu-.js → use-reply-chain-data-DaK8q2qQ.js} +1 -1
  246. package/core/built/admin/assets/use-setting-group-YlYcoNyP.js +1 -0
  247. package/core/built/admin/assets/use-settings-navigation-Bf1g1rEh.js +1 -0
  248. package/core/built/admin/assets/{use-simple-pagination-CjYNxdwe.js → use-simple-pagination-D9ckKo5-.js} +1 -1
  249. package/core/built/admin/assets/{use-tinybird-query-falHAtuK.js → use-tinybird-query-GSa32hVw.js} +1 -1
  250. package/core/built/admin/assets/{use-tinybird-token-D7-8ND1y.js → use-tinybird-token-CKLOZFsw.js} +1 -1
  251. package/core/built/admin/assets/use-url-input-z-p0Kewm.js +5 -0
  252. package/core/built/admin/assets/user-detail-modal-BMxOA4Sv.js +1 -0
  253. package/core/built/admin/assets/{users-Dkv8NQZe.js → users-B6hIds6n.js} +1 -1
  254. package/core/built/admin/assets/validator-Cr0sMKuW.js +7 -0
  255. package/core/built/admin/assets/{virtual-list-CvnC9zF2.js → virtual-list-RHeEL05L.js} +1 -1
  256. package/core/built/admin/assets/{web-jFYWCKDn.js → web-Bw2QMO5u.js} +2 -2
  257. package/core/built/admin/assets/web-xJtcwMy4.js +1 -0
  258. package/core/built/admin/assets/zapier-modal-BP2f4-vY.js +1 -0
  259. package/core/built/admin/index.html +20 -18
  260. package/core/frontend/helpers/authors.js +1 -1
  261. package/core/frontend/helpers/link.js +3 -8
  262. package/core/frontend/helpers/tags.js +1 -1
  263. package/core/frontend/meta/author-url.js +2 -2
  264. package/core/frontend/meta/url.js +4 -4
  265. package/core/frontend/services/data/fetch-data.js +10 -5
  266. package/core/frontend/services/proxy.js +1 -1
  267. package/core/frontend/services/routing/api-adapter.js +115 -0
  268. package/core/frontend/services/routing/api-adapter.ts +157 -0
  269. package/core/frontend/services/routing/collection-router.js +5 -3
  270. package/core/frontend/services/routing/controllers/index.js +1 -1
  271. package/core/frontend/services/routing/controllers/rss.js +26 -31
  272. package/core/frontend/services/routing/controllers/rss.ts +66 -0
  273. package/core/frontend/services/routing/controllers/static.js +8 -7
  274. package/core/frontend/services/routing/events.js +20 -6
  275. package/core/frontend/services/routing/parent-router.js +17 -4
  276. package/core/frontend/services/routing/permalink-adapter.js +13 -0
  277. package/core/frontend/services/routing/permalink-adapter.ts +10 -0
  278. package/core/frontend/services/routing/registry.js +59 -33
  279. package/core/frontend/services/routing/router-manager.js +24 -22
  280. package/core/frontend/services/routing/static-routes-router.js +4 -4
  281. package/core/frontend/services/routing/taxonomy-router.js +6 -2
  282. package/core/frontend/services/sitemap/base-site-map-generator.js +1 -28
  283. package/core/frontend/services/sitemap/site-map-manager.js +22 -57
  284. package/core/server/adapters/route-settings/utils.js +10 -2
  285. package/core/server/adapters/route-settings/utils.ts +12 -2
  286. package/core/server/api/endpoints/email-post.js +1 -1
  287. package/core/server/api/endpoints/previews.js +1 -1
  288. package/core/server/api/endpoints/search-index-public.js +3 -3
  289. package/core/server/api/endpoints/search-index.js +4 -4
  290. package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -2
  291. package/core/server/api/endpoints/utils/serializers/input/comments.js +1 -1
  292. package/core/server/api/endpoints/utils/serializers/input/pages.js +3 -3
  293. package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
  294. package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
  295. package/core/server/api/endpoints/utils/serializers/input/users.js +2 -2
  296. package/core/server/api/endpoints/utils/serializers/input/utils/url.js +31 -13
  297. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +2 -2
  298. package/core/server/api/endpoints/utils/serializers/output/mappers/tags.js +2 -2
  299. package/core/server/api/endpoints/utils/serializers/output/mappers/users.js +8 -0
  300. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +18 -11
  301. package/core/server/data/migrations/versions/6.56/2026-08-03-19-27-51-add-automation-run-step-id-to-automated-email-recipients.js +8 -0
  302. package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
  303. package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +74 -0
  304. package/core/server/data/migrations/versions/6.57/2026-08-04-18-54-14-reset-automation-email-analytics.js +25 -0
  305. package/core/server/data/schema/commands.js +21 -5
  306. package/core/server/data/schema/fixtures/fixtures.json +2 -2
  307. package/core/server/data/schema/schema.js +25 -5
  308. package/core/server/lib/common/to-plain.ts +1 -1
  309. package/core/server/models/post.js +12 -3
  310. package/core/server/services/adapter-manager/adapter-manager.js +1 -45
  311. package/core/server/services/adapter-manager/adapter-manager.ts +2 -48
  312. package/core/server/services/adapter-manager/adapter-paths.js +24 -0
  313. package/core/server/services/adapter-manager/adapter-paths.ts +21 -0
  314. package/core/server/services/adapter-manager/base-classes.js +22 -0
  315. package/core/server/services/adapter-manager/base-classes.ts +22 -0
  316. package/core/server/services/adapter-manager/index.js +4 -27
  317. package/core/server/services/adapter-manager/index.ts +4 -31
  318. package/core/server/services/adapter-manager/utils.js +50 -0
  319. package/core/server/services/adapter-manager/utils.ts +57 -0
  320. package/core/server/services/audience-feedback/audience-feedback-controller.js +1 -1
  321. package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
  322. package/core/server/services/automations/automations-repository.ts +3 -1
  323. package/core/server/services/automations/database-automations-repository.js +33 -14
  324. package/core/server/services/automations/database-automations-repository.ts +41 -15
  325. package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
  326. package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
  327. package/core/server/services/automations/poll.js +3 -1
  328. package/core/server/services/automations/poll.ts +4 -1
  329. package/core/server/services/automations/service.js +7 -3
  330. package/core/server/services/automations/service.ts +7 -3
  331. package/core/server/services/comments/comments-service-emails.js +1 -1
  332. package/core/server/services/comments/comments-service.js +1 -1
  333. package/core/server/services/email-analytics/email-analytics-service-wrapper.js +11 -2
  334. package/core/server/services/email-analytics/email-analytics-service.js +1 -1
  335. package/core/server/services/email-service/email-service-wrapper.js +2 -1
  336. package/core/server/services/email-service/mailgun-email-provider.js +15 -0
  337. package/core/server/services/gift-links/CONTEXT.md +9 -0
  338. package/core/server/services/gifts/CONTEXT.md +55 -0
  339. package/core/server/services/gifts/README.md +28 -0
  340. package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
  341. package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
  342. package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
  343. package/core/server/services/gifts/gift-checkout-offer.js +74 -0
  344. package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
  345. package/core/server/services/gifts/gift-codec.js +63 -0
  346. package/core/server/services/gifts/gift-codec.ts +62 -0
  347. package/core/server/services/gifts/gift-controller.js +6 -31
  348. package/core/server/services/gifts/gift-controller.ts +6 -69
  349. package/core/server/services/gifts/gift-email-service.ts +3 -2
  350. package/core/server/services/gifts/gift-schema.js +34 -0
  351. package/core/server/services/gifts/gift-schema.ts +39 -0
  352. package/core/server/services/gifts/gift-service-wrapper.js +13 -11
  353. package/core/server/services/gifts/gift-service.js +265 -52
  354. package/core/server/services/gifts/gift-service.ts +431 -97
  355. package/core/server/services/gifts/gift.ts +16 -39
  356. package/core/server/services/indexnow-ping/indexnow-ping-service.js +1 -1
  357. package/core/server/services/indexnow-ping/indexnow-ping-service.ts +2 -4
  358. package/core/server/services/lib/mailgun-client.js +3 -3
  359. package/core/server/services/link-tracking/link-click-repository.js +6 -1
  360. package/core/server/services/link-tracking/link-click-tracking-service.js +12 -3
  361. package/core/server/services/media-inliner/external-media-inliner.js +12 -8
  362. package/core/server/services/member-attribution/url-translator.js +5 -7
  363. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +4 -3
  364. package/core/server/services/member-welcome-emails/service.js +79 -15
  365. package/core/server/services/members/api.js +2 -3
  366. package/core/server/services/members/import-export/import/completion-email.js +8 -4
  367. package/core/server/services/members/import-export/import/completion-email.ts +9 -4
  368. package/core/server/services/members/import-export/import/importer.js +25 -4
  369. package/core/server/services/members/import-export/import/importer.ts +27 -6
  370. package/core/server/services/members/import-export/import/row.ts +5 -1
  371. package/core/server/services/members/import-export/index.js +6 -2
  372. package/core/server/services/members/import-export/index.ts +10 -4
  373. package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
  374. package/core/server/services/members/members-api/members-api.js +13 -7
  375. package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
  376. package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
  377. package/core/server/services/members/members-api/services/payments-service.js +6 -68
  378. package/core/server/services/members-custom-fields/queries.js +4 -13
  379. package/core/server/services/members-custom-fields/queries.ts +4 -13
  380. package/core/server/services/members-custom-fields/schema.js +19 -21
  381. package/core/server/services/members-custom-fields/schema.ts +18 -20
  382. package/core/server/services/members-custom-fields/storage.js +94 -27
  383. package/core/server/services/members-custom-fields/storage.ts +114 -39
  384. package/core/server/services/members-custom-fields/values-service.js +98 -103
  385. package/core/server/services/members-custom-fields/values-service.ts +116 -114
  386. package/core/server/services/mentions/resource-service.js +1 -1
  387. package/core/server/services/mentions/service.js +2 -2
  388. package/core/server/services/oembed/oembed-service.js +44 -3
  389. package/core/server/services/posts/posts-service.js +11 -0
  390. package/core/server/services/route-settings/dynamic-routing-service.js +10 -62
  391. package/core/server/services/route-settings/route-settings-parser.js +25 -25
  392. package/core/server/services/route-settings/route-settings-parser.ts +27 -27
  393. package/core/server/services/route-settings/validation-errors.js +68 -4
  394. package/core/server/services/route-settings/validation-errors.ts +79 -4
  395. package/core/server/services/slack-ping/slack-ping-service.js +2 -2
  396. package/core/server/services/slack-ping/slack-ping-service.ts +3 -5
  397. package/core/server/services/stats/content-stats-service.js +3 -3
  398. package/core/server/services/stats/posts-stats-service.js +29 -25
  399. package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
  400. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
  401. package/core/server/services/stripe/stripe-api.js +10 -0
  402. package/core/server/services/url/config.js +90 -135
  403. package/core/server/services/url/index.js +6 -49
  404. package/core/server/services/url/lazy-find-resource.js +14 -11
  405. package/core/server/services/url/lazy-find-resource.ts +14 -11
  406. package/core/server/services/url/lazy-url-service.js +174 -94
  407. package/core/server/services/url/lazy-url-service.ts +233 -97
  408. package/core/server/services/url/routable-resources.js +62 -71
  409. package/core/server/services/url/router-filter.js +12 -4
  410. package/core/server/services/url/router-filter.ts +12 -4
  411. package/core/server/services/webhooks/index.js +1 -1
  412. package/core/server/services/webhooks/serialize.js +2 -1
  413. package/core/server/web/gift-preview/controller.js +10 -25
  414. package/core/shared/config/env/config.testing-mysql.json +1 -2
  415. package/core/shared/config/env/config.testing.json +1 -2
  416. package/core/shared/events/index.js +1 -2
  417. package/core/shared/labs.js +4 -4
  418. package/package.json +10 -10
  419. package/pnpm-lock.yaml +27 -20
  420. package/pnpm-workspace.yaml +3 -2
  421. package/tsconfig.json +1 -0
  422. package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
  423. package/core/built/admin/assets/Combination-CJG7Flp-.js +0 -41
  424. package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +0 -1
  425. package/core/built/admin/assets/automated-emails-DRy7qY-J.js +0 -7
  426. package/core/built/admin/assets/checkbox-CWa28lgO.js +0 -1
  427. package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +0 -25
  428. package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +0 -27
  429. package/core/built/admin/assets/code-editor-view-CCr0rBWS.js +0 -1
  430. package/core/built/admin/assets/comments-DyUSMUdj.js +0 -1
  431. package/core/built/admin/assets/disabled-sources-indicator-CUxMfiPa.js +0 -1
  432. package/core/built/admin/assets/dist-2Tnz2Yfq.js +0 -1
  433. package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
  434. package/core/built/admin/assets/dist-DDY6GRVz.js +0 -1
  435. package/core/built/admin/assets/dist-DG78yroX.js +0 -23
  436. package/core/built/admin/assets/domain-C0bV2UV8.js +0 -1
  437. package/core/built/admin/assets/editor-Dp865O-4.js +0 -8
  438. package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
  439. package/core/built/admin/assets/filter-sources-BV2rnpfA.js +0 -1
  440. package/core/built/admin/assets/filters-CUXJ2Nm9.js +0 -1
  441. package/core/built/admin/assets/filters-DfbIWc_l.js +0 -1
  442. package/core/built/admin/assets/index-9Cw8z3rA.css +0 -1
  443. package/core/built/admin/assets/index-BGG7NckO.js +0 -67
  444. package/core/built/admin/assets/input-group-BSgG_lVw.js +0 -1
  445. package/core/built/admin/assets/label-picker-1DvSuLA1.js +0 -1
  446. package/core/built/admin/assets/labels-CDSUt4J_.js +0 -1
  447. package/core/built/admin/assets/layout-yQEi2qDM.js +0 -1
  448. package/core/built/admin/assets/link-helpers-7EYqvvHs.js +0 -1
  449. package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +0 -1
  450. package/core/built/admin/assets/member-detail-BAq7gDaa.js +0 -1
  451. package/core/built/admin/assets/members-CQpVFJu4.js +0 -10
  452. package/core/built/admin/assets/modals-ReORvWnu.js +0 -31
  453. package/core/built/admin/assets/newsletter-CJQvtHqN.js +0 -1
  454. package/core/built/admin/assets/newsletters-B3QQXfGT.js +0 -1
  455. package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
  456. package/core/built/admin/assets/overview-Bf2sbznq.js +0 -1
  457. package/core/built/admin/assets/overview-CBfePxcr.js +0 -1
  458. package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +0 -1
  459. package/core/built/admin/assets/preferences-DJPfHEGu.js +0 -5
  460. package/core/built/admin/assets/profile-DqBQp-gT.js +0 -1
  461. package/core/built/admin/assets/select-C5_xEt5b.js +0 -1
  462. package/core/built/admin/assets/settings-BwiVIXau.js +0 -4
  463. package/core/built/admin/assets/social-urls-BQPFEk1_.js +0 -8
  464. package/core/built/admin/assets/switch-z5Y6Uzt6.js +0 -1
  465. package/core/built/admin/assets/tabs-CCEgKYzq.js +0 -1
  466. package/core/built/admin/assets/tags-Cg2VJGmG.js +0 -1
  467. package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
  468. package/core/built/admin/assets/toggle-group-b3IlxvhV.js +0 -1
  469. package/core/built/admin/assets/web-B_5GZRvH.js +0 -1
  470. package/core/server/services/gifts/gift-repository.js +0 -2
  471. package/core/server/services/gifts/gift-repository.ts +0 -29
  472. package/core/server/services/route-settings/activation-bridge.js +0 -174
  473. package/core/server/services/route-settings/activation-bridge.ts +0 -250
  474. package/core/server/services/url/local-file-cache.js +0 -75
  475. package/core/server/services/url/queue.js +0 -260
  476. package/core/server/services/url/resource.js +0 -102
  477. package/core/server/services/url/resources.js +0 -480
  478. package/core/server/services/url/url-generator.js +0 -276
  479. package/core/server/services/url/url-service-facade.js +0 -366
  480. package/core/server/services/url/url-service-facade.ts +0 -519
  481. package/core/server/services/url/url-service.js +0 -387
  482. package/core/server/services/url/urls.js +0 -140
  483. package/core/shared/events/url-resource-updated-event.js +0 -33
@@ -1,7 +1,7 @@
1
1
  import {GIFT_EXPIRY_DAYS} from './constants';
2
+ import type {GiftCadence, GiftData, GiftStatus} from './gift-schema';
2
3
 
3
- export type GiftStatus = 'purchased' | 'redeemed' | 'consumed' | 'expired' | 'refunded';
4
- export type GiftCadence = 'month' | 'year';
4
+ export type {GiftCadence, GiftStatus} from './gift-schema';
5
5
 
6
6
  export type RedeemableCheckFailureReason = 'redeemed' | 'consumed' | 'expired' | 'refunded' | 'paid-member';
7
7
  export type RedeemableCheckResult =
@@ -13,43 +13,20 @@ export type ReassignableCheckResult =
13
13
  | {reassignable: true}
14
14
  | {reassignable: false; reason: ReassignableCheckFailureReason};
15
15
 
16
- interface GiftData {
17
- token: string;
18
- buyerEmail: string;
19
- buyerMemberId: string | null;
20
- redeemerMemberId: string | null;
21
- tierId: string;
22
- cadence: GiftCadence;
23
- duration: number;
24
- currency: string;
25
- amount: number;
26
- stripeCheckoutSessionId: string;
27
- stripePaymentIntentId: string;
28
- consumesAt: Date | null;
29
- expiresAt: Date;
30
- status: GiftStatus;
31
- purchasedAt: Date;
32
- redeemedAt: Date | null;
33
- consumedAt: Date | null;
34
- expiredAt: Date | null;
35
- refundedAt: Date | null;
36
- consumesSoonReminderSentAt: Date | null;
37
- }
38
-
39
- export interface GiftFromPurchaseData {
40
- token: string;
41
- buyerEmail: string;
42
- buyerMemberId: string | null;
43
- tierId: string;
44
- cadence: GiftCadence;
45
- duration: number;
46
- currency: string;
47
- amount: number;
48
- stripeCheckoutSessionId: string;
49
- stripePaymentIntentId: string;
50
- }
51
-
52
- export class Gift {
16
+ export type GiftFromPurchaseData = Pick<GiftData,
17
+ | 'token'
18
+ | 'buyerEmail'
19
+ | 'buyerMemberId'
20
+ | 'tierId'
21
+ | 'cadence'
22
+ | 'duration'
23
+ | 'currency'
24
+ | 'amount'
25
+ | 'stripeCheckoutSessionId'
26
+ | 'stripePaymentIntentId'
27
+ >;
28
+
29
+ export class Gift implements GiftData {
53
30
  token: string;
54
31
  buyerEmail: string;
55
32
  buyerMemberId: string | null;
@@ -94,7 +94,7 @@ class IndexNowPingService {
94
94
  // publishing.
95
95
  let url = null;
96
96
  try {
97
- url = this.urlService.facade.getUrlForResource({ ...post, type: 'posts' }, { absolute: true });
97
+ url = this.urlService.getUrlForResource({ ...post, type: 'posts' }, { absolute: true });
98
98
  if (!url || url.endsWith('/404/')) {
99
99
  this.logging.warn({
100
100
  event: { name: 'indexnow.unresolved_url' },
@@ -44,9 +44,7 @@ type Labs = {
44
44
  };
45
45
 
46
46
  type UrlService = {
47
- facade: {
48
- getUrlForResource(resource: Record<string, unknown>, options: {absolute: boolean}): string | null;
49
- };
47
+ getUrlForResource(resource: Record<string, unknown>, options: {absolute: boolean}): string | null;
50
48
  };
51
49
 
52
50
  type UrlUtils = {
@@ -170,7 +168,7 @@ export class IndexNowPingService {
170
168
  // publishing.
171
169
  let url: string | null = null;
172
170
  try {
173
- url = this.urlService.facade.getUrlForResource({...post, type: 'posts'}, {absolute: true});
171
+ url = this.urlService.getUrlForResource({...post, type: 'posts'}, {absolute: true});
174
172
 
175
173
  if (!url || url.endsWith('/404/')) {
176
174
  this.logging.warn({
@@ -85,9 +85,9 @@ module.exports = class MailgunClient {
85
85
  messageData['v:email-id'] = message.id;
86
86
  }
87
87
 
88
- const tags = ['bulk-email', 'ghost-email'];
89
- if (bulkEmailConfig?.mailgun?.tag) {
90
- tags.push(bulkEmailConfig.mailgun.tag);
88
+ const tags = ['ghost-email'];
89
+ if (Array.isArray(message.tags)) {
90
+ tags.push(...message.tags);
91
91
  }
92
92
  messageData['o:tag'] = tags;
93
93
 
@@ -32,7 +32,12 @@ module.exports = class LinkClickRepository {
32
32
 
33
33
  // Memoize the findOne function
34
34
  this.memoizedFindOne = _.memoize(async (uuid, options) => {
35
- return await this.#Member.findOne({uuid}, options);
35
+ try {
36
+ return await this.#Member.findOne({uuid}, options);
37
+ } catch (error) {
38
+ this.memoizedFindOne.cache.delete(uuid);
39
+ throw error;
40
+ }
36
41
  });
37
42
  }
38
43
 
@@ -4,6 +4,7 @@ const PostLink = require('./post-link');
4
4
  const ObjectID = require('bson-objectid').default;
5
5
  const errors = require('@tryghost/errors');
6
6
  const nql = require('@tryghost/nql');
7
+ const {escapeNqlString} = require('@tryghost/nql-string');
7
8
  const _ = require('lodash');
8
9
  const tpl = require('@tryghost/tpl');
9
10
  const moment = require('moment');
@@ -174,7 +175,11 @@ class LinkClickTrackingService {
174
175
 
175
176
  // manages transformation of current url to relative for comparision
176
177
  const transformedOldUrl = this.#urlUtils.absoluteToTransformReady(redirectUrl.href);
177
- const filterQuery = `post_id:'${postId}'+to:'${transformedOldUrl}'`;
178
+ // the url is re-serialised into NQL here, so it needs escaping again -
179
+ // quotes are legal in a URL and would otherwise break the filter.
180
+ // postId is String()d because nql parses an unquoted `post_id:123` to a
181
+ // number, and escapeNqlString operates on strings
182
+ const filterQuery = `post_id:${escapeNqlString(String(postId))}+to:${escapeNqlString(transformedOldUrl)}`;
178
183
 
179
184
  const updatedFilterOptions = {
180
185
  ...filterOptions,
@@ -245,13 +250,15 @@ class LinkClickTrackingService {
245
250
  * Add first-party tracking to a URL in an automation email
246
251
  * @param {Readonly<URL>} url
247
252
  * @param {string} automationActionRevisionId
253
+ * @param {string} automationRunStepId
248
254
  * @param {string} memberUuid
249
255
  * @return {Promise<URL>}
250
256
  */
251
- async addAutomationTrackingToUrl(url, automationActionRevisionId, memberUuid) {
257
+ async addAutomationTrackingToUrl(url, automationActionRevisionId, automationRunStepId, memberUuid) {
252
258
  const redirect = await this.#linkRedirectService.getOrAddAutomationRedirect(automationActionRevisionId, url);
253
259
  const trackedUrl = new URL(redirect.from.href);
254
260
  trackedUrl.searchParams.set('m', memberUuid);
261
+ trackedUrl.searchParams.set('step', automationRunStepId);
255
262
  return trackedUrl;
256
263
  }
257
264
 
@@ -269,7 +276,8 @@ class LinkClickTrackingService {
269
276
  });
270
277
 
271
278
  const automationActionRevisionId = event.data.link.automationActionRevisionId;
272
- if (!automationActionRevisionId) {
279
+ const automationRunStepId = event.data.url.searchParams.get('step');
280
+ if (!automationActionRevisionId || !automationRunStepId) {
273
281
  await this.#linkClickRepository.save(click);
274
282
  return;
275
283
  }
@@ -282,6 +290,7 @@ class LinkClickTrackingService {
282
290
 
283
291
  await this.#automationsApi.trackEmailClicked({
284
292
  automationActionRevisionId,
293
+ automationRunStepId,
285
294
  memberId,
286
295
  clickedAt: event.timestamp
287
296
  }, {transacting});
@@ -5,7 +5,6 @@ const errors = require('@tryghost/errors');
5
5
  const logging = require('@tryghost/logging');
6
6
  const string = require('@tryghost/string');
7
7
  const path = require('path');
8
- const convert = require('heic-convert');
9
8
 
10
9
  let fileTypeFromBuffer;
11
10
 
@@ -102,20 +101,25 @@ class ExternalMediaInliner {
102
101
  }
103
102
 
104
103
  // If the file is heic or heif, attempt to convert it to jpeg
105
- try {
106
- if (extension === 'heic' || extension === 'heif') {
104
+ if (extension === 'heic' || extension === 'heif') {
105
+ // Lazy: pulls in libheif-js, a WASM codec costing ~50ms to load at boot.
106
+ // Deliberately outside the try — a missing codec is not a conversion error,
107
+ // and swallowing it would store an unconvertible .heic in its place.
108
+ const convert = require('heic-convert');
109
+
110
+ try {
107
111
  body = await convert({
108
112
  buffer: body,
109
113
  format: 'JPEG'
110
114
  });
111
115
 
112
116
  extension = 'jpg';
117
+ } catch (error) {
118
+ logging.error(`Error converting file to JPEG: ${requestURL}`);
119
+ logging.error(new errors.DataImportError({
120
+ err: error
121
+ }));
113
122
  }
114
- } catch (error) {
115
- logging.error(`Error converting file to JPEG: ${requestURL}`);
116
- logging.error(new errors.DataImportError({
117
- err: error
118
- }));
119
123
  }
120
124
 
121
125
  const removeExtRegExp = new RegExp(`.${extension}`, '');
@@ -1,9 +1,7 @@
1
1
  /**
2
2
  * @typedef {Object} UrlService
3
- * @prop {{
4
- * getUrlForResource: (resource: Object, options: Object) => string,
5
- * resolveUrl: (path: string) => Promise<Object | null>
6
- * }} facade
3
+ * @prop {(resource: Object, options: Object) => string} getUrlForResource
4
+ * @prop {(path: string) => Promise<Object | null>} resolveUrl
7
5
  */
8
6
 
9
7
  const {toPlain} = require('../../lib/common/to-plain');
@@ -106,7 +104,7 @@ class UrlTranslator {
106
104
  // surface as an error.
107
105
  let resource;
108
106
  try {
109
- resource = await this.urlService.facade.resolveUrl(path);
107
+ resource = await this.urlService.resolveUrl(path);
110
108
  } catch (err) {
111
109
  return;
112
110
  }
@@ -155,10 +153,10 @@ class UrlTranslator {
155
153
  }
156
154
  // Lazy URL service evaluates permalink templates against resource fields
157
155
  // (slug, published_at, primary_tag, ...). Caller already loaded the model,
158
- // so spread its plain data so those fields reach the facade.
156
+ // so spread its plain data so those fields reach the URL service.
159
157
  const data = toPlain(model);
160
158
  const resource = {...data, id, type: TYPE_TO_RESOURCE[type]};
161
- return this.urlService.facade.getUrlForResource(resource, {absolute});
159
+ return this.urlService.getUrlForResource(resource, {absolute});
162
160
  }
163
161
 
164
162
  async getResourceById(id, type) {
@@ -119,9 +119,10 @@ class MemberWelcomeEmailRenderer {
119
119
  * @param {string} [options.unsubscribeUrl] - When set, the footer shows an "Unsubscribe from these emails" link instead of "Manage your preferences"
120
120
  * @param {boolean} [options.trackClicks]
121
121
  * @param {string | null} [options.automationActionRevisionId]
122
+ * @param {string | null} [options.automationRunStepId]
122
123
  * @returns {Promise<{html: string, text: string, subject: string}>}
123
124
  */
124
- async render({lexical, subject, designSettings, member, siteSettings, unsubscribeUrl, trackClicks = false, automationActionRevisionId = null}) {
125
+ async render({lexical, subject, designSettings, member, siteSettings, unsubscribeUrl, trackClicks = false, automationActionRevisionId = null, automationRunStepId = null}) {
125
126
  designSettings = designSettings || {};
126
127
 
127
128
  const design = emailDesign.getEmailDesign({
@@ -171,9 +172,9 @@ class MemberWelcomeEmailRenderer {
171
172
  return originalPath;
172
173
  }
173
174
  const isTrackable = ['http:', 'https:'].includes(url.protocol);
174
- if (trackClicks && automationActionRevisionId && member.uuid && isTrackable) {
175
+ if (trackClicks && automationActionRevisionId && automationRunStepId && member.uuid && isTrackable) {
175
176
  await linkTracking.init();
176
- return await linkTracking.service.addAutomationTrackingToUrl(url, automationActionRevisionId, member.uuid);
177
+ return await linkTracking.service.addAutomationTrackingToUrl(url, automationActionRevisionId, automationRunStepId, member.uuid);
177
178
  }
178
179
  return url;
179
180
  }, {base: siteSettings.url});
@@ -9,6 +9,8 @@ const emailAddressService = require('../email-address');
9
9
  const settingsHelpers = require('../settings-helpers');
10
10
  const EmailAddressParser = require('../email-address/email-address-parser');
11
11
  const mail = require('../mail');
12
+ const MailgunClient = require('../lib/mailgun-client');
13
+ const config = require('../../../shared/config');
12
14
  const labs = require('../../../shared/labs');
13
15
  const {Automation, EmailDesignSetting, Newsletter} = require('../../models');
14
16
  const MemberWelcomeEmailRenderer = require('./member-welcome-email-renderer');
@@ -46,7 +48,8 @@ const getSenderDetails = (designSettingsJson) => {
46
48
  };
47
49
 
48
50
  class MemberWelcomeEmailService {
49
- #mailer;
51
+ #transactionalMailer;
52
+ #bulkMailer;
50
53
  #renderer;
51
54
  #magicLinkService;
52
55
  #memberWelcomeEmails = {free: null, paid: null};
@@ -54,7 +57,8 @@ class MemberWelcomeEmailService {
54
57
 
55
58
  constructor({t, dir, singleUseTokenProvider}) {
56
59
  emailAddressService.init();
57
- this.#mailer = new mail.GhostMailer();
60
+ this.#transactionalMailer = new mail.GhostMailer();
61
+ this.#bulkMailer = new MailgunClient({config, settings: settingsCache});
58
62
  this.#renderer = new MemberWelcomeEmailRenderer({t, dir});
59
63
 
60
64
  const getSigninURL = (token) => {
@@ -306,7 +310,7 @@ class MemberWelcomeEmailService {
306
310
  logging.warn(message.text);
307
311
  }
308
312
 
309
- return this.#mailer.send({
313
+ return this.#transactionalMailer.send({
310
314
  from: fromEmail,
311
315
  subject: 'Verify email address',
312
316
  forceTextContent: true,
@@ -370,10 +374,11 @@ class MemberWelcomeEmailService {
370
374
  * @param {boolean} [options.trackOpens]
371
375
  * @param {boolean} [options.trackClicks]
372
376
  * @param {string | null} [options.automationActionRevisionId]
377
+ * @param {string | null} [options.automationRunStepId]
373
378
  * @param {null | {url: string, oneClickUrl: string}} [options.unsubscribe] - When set, the footer links to an unsubscribe URL and the email carries one-click List-Unsubscribe headers
374
379
  * @returns {Promise<unknown>}
375
380
  */
376
- async #sendEmail({member, memberStatus, email, emailType, trackOpens, trackClicks = false, automationActionRevisionId = null, unsubscribe = null}) {
381
+ async #sendEmail({member, memberStatus, email, emailType, trackOpens, trackClicks = false, automationActionRevisionId = null, automationRunStepId = null, unsubscribe = null}) {
377
382
  if (!member.email) {
378
383
  throw new errors.IncorrectUsageError({
379
384
  message: MESSAGES.MISSING_RECIPIENT_EMAIL
@@ -400,25 +405,24 @@ class MemberWelcomeEmailService {
400
405
  siteSettings: this.#getSiteSettings(),
401
406
  unsubscribeUrl: unsubscribe?.url,
402
407
  trackClicks,
403
- automationActionRevisionId
408
+ automationActionRevisionId,
409
+ automationRunStepId
404
410
  });
405
411
 
406
412
  const senderOptions = await this.#getEffectiveSenderOptions(
407
413
  getSenderDetails(email.designSettings)
408
414
  );
409
415
 
410
- const headers = unsubscribe?.oneClickUrl ? {
411
- 'List-Unsubscribe': `<${unsubscribe.oneClickUrl}>`,
412
- 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
413
- } : undefined;
414
-
415
416
  /** @type {string[]} */ let tags;
417
+ let sendEmail;
416
418
  switch (emailType) {
417
419
  case 'welcome':
418
420
  tags = [MEMBER_WELCOME_EMAIL_TAG];
421
+ sendEmail = this.#sendTransactionalEmail.bind(this);
419
422
  break;
420
423
  case 'automation':
421
424
  tags = [AUTOMATION_EMAIL_TAG];
425
+ sendEmail = this.#sendBulkEmail.bind(this);
422
426
  break;
423
427
  default: {
424
428
  /** @type {never} */ const _exhaustive = emailType;
@@ -428,19 +432,78 @@ class MemberWelcomeEmailService {
428
432
  }
429
433
  }
430
434
 
431
- return await this.#mailer.send({
435
+ return await sendEmail({
432
436
  to: member.email,
433
437
  subject,
434
438
  html,
435
439
  text,
440
+ tags,
441
+ trackOpens,
442
+ listUnsubscribe: unsubscribe?.oneClickUrl,
443
+ ...senderOptions
444
+ });
445
+ }
446
+
447
+ /**
448
+ * @param {object} options
449
+ * @param {string} options.to
450
+ * @param {string} options.subject
451
+ * @param {string} options.html
452
+ * @param {string} options.text
453
+ * @param {string} options.from
454
+ * @param {string} [options.replyTo]
455
+ * @param {string[]} [options.tags]
456
+ * @param {boolean} [options.trackOpens]
457
+ * @param {string} [options.listUnsubscribe]
458
+ * @returns {Promise<unknown>}
459
+ */
460
+ async #sendTransactionalEmail({to, subject, html, text, from, replyTo, tags, trackOpens, listUnsubscribe}) {
461
+ const headers = listUnsubscribe ? {
462
+ 'List-Unsubscribe': `<${listUnsubscribe}>`,
463
+ 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
464
+ } : undefined;
465
+
466
+ return await this.#transactionalMailer.send({
467
+ to,
468
+ subject,
469
+ html,
470
+ text,
436
471
  forceTextContent: true,
437
472
  tags,
438
473
  ...(headers ? {headers} : {}),
439
474
  ...(typeof trackOpens === 'boolean' ? {trackOpens} : {}),
440
- ...senderOptions
475
+ from,
476
+ replyTo
441
477
  });
442
478
  }
443
479
 
480
+ /**
481
+ * @param {object} options
482
+ * @param {string} options.to
483
+ * @param {string} options.subject
484
+ * @param {string} options.html
485
+ * @param {string} options.text
486
+ * @param {string} options.from
487
+ * @param {string} [options.replyTo]
488
+ * @param {string[]} [options.tags]
489
+ * @param {boolean} [options.trackOpens]
490
+ * @param {string} [options.listUnsubscribe]
491
+ * @returns {Promise<unknown>}
492
+ */
493
+ async #sendBulkEmail({to, subject, html, text, from, replyTo, tags, trackOpens, listUnsubscribe}) {
494
+ return await this.#bulkMailer.send({
495
+ subject,
496
+ html,
497
+ plaintext: text,
498
+ from,
499
+ replyTo,
500
+ tags,
501
+ ...(typeof trackOpens === 'boolean' ? {track_opens: trackOpens} : {})
502
+ }, {
503
+ [to]: listUnsubscribe ? {list_unsubscribe: listUnsubscribe} : {}
504
+ }, []);
505
+ }
506
+
444
507
  async send({member, memberStatus}) {
445
508
  const email = this.#memberWelcomeEmails[memberStatus];
446
509
 
@@ -480,9 +543,10 @@ class MemberWelcomeEmailService {
480
543
  * @param {boolean} options.trackOpens
481
544
  * @param {boolean} options.trackClicks
482
545
  * @param {string} options.automationActionRevisionId
546
+ * @param {string} options.automationRunStepId
483
547
  * @returns {Promise<unknown>}
484
548
  */
485
- async sendAutomationEmail({email, member, memberStatus, trackOpens, trackClicks, automationActionRevisionId}) {
549
+ async sendAutomationEmail({email, member, memberStatus, trackOpens, trackClicks, automationActionRevisionId, automationRunStepId}) {
486
550
  const designSettings = email.designSettingId ?
487
551
  await EmailDesignSetting.findOne({id: email.designSettingId}) :
488
552
  null;
@@ -510,6 +574,7 @@ class MemberWelcomeEmailService {
510
574
  trackOpens,
511
575
  trackClicks,
512
576
  automationActionRevisionId,
577
+ automationRunStepId,
513
578
  unsubscribe
514
579
  });
515
580
  }
@@ -647,12 +712,11 @@ class MemberWelcomeEmailService {
647
712
  getSenderDetails(designSettingsJson)
648
713
  );
649
714
 
650
- await this.#mailer.send({
715
+ await this.#sendTransactionalEmail({
651
716
  to: email,
652
717
  subject: `[Test] ${renderedSubject}`,
653
718
  html,
654
719
  text,
655
- forceTextContent: true,
656
720
  ...senderOptions
657
721
  });
658
722
  }
@@ -54,7 +54,7 @@ function trimLeadingWhitespace(strings, ...values) {
54
54
 
55
55
  function createApiInstance(config) {
56
56
  const membersApiInstance = MembersApi({
57
- urlService: urlService.facade,
57
+ urlService,
58
58
  tokenConfig: config.getTokenConfig(),
59
59
  auth: {
60
60
  getSigninURL: config.getSigninURL.bind(config),
@@ -244,8 +244,7 @@ function createApiInstance(config) {
244
244
  EmailSpamComplaintEvent: models.EmailSpamComplaintEvent,
245
245
  Automation: models.Automation,
246
246
  WelcomeEmailAutomationRun: models.WelcomeEmailAutomationRun,
247
- AutomatedEmailRecipient: models.AutomatedEmailRecipient,
248
- Gift: models.Gift
247
+ AutomatedEmailRecipient: models.AutomatedEmailRecipient
249
248
  },
250
249
  stripeAPIService: stripeService.api,
251
250
  tiersService: tiersService,
@@ -6,14 +6,18 @@ const csv_2 = require("@tryghost/custom-field-types/csv");
6
6
  const emailTemplate = require('./email-template');
7
7
  // Turn an ORM validation error into copy a member manager can act on. Presentation
8
8
  // only: the API response keeps the raw messages, and just the emailed report is
9
- // rewritten.
10
- function humaniseError(message) {
9
+ // rewritten. Takes one reason at a time, so a rewrite matching to the end of its input
10
+ // cannot consume the reason after it.
11
+ function humaniseReason(message) {
11
12
  return message
12
13
  .replace('Value in [members.email] cannot be blank.', 'Missing email address')
13
14
  .replace('Value in [members.note] exceeds maximum length of 2000 characters.', '"Note" exceeds maximum length of 2000 characters')
14
15
  .replace('Value in [members.subscribed] must be one of true, false, 0 or 1.', 'Value in "Subscribed to emails" must be "true" or "false"')
15
16
  .replace('Validation (isEmail) failed for email', 'Invalid email address')
16
- .replace(/No such customer:[^,]*/, 'Could not find Stripe customer');
17
+ .replace(/No such customer:[\s\S]*/, 'Could not find Stripe customer');
18
+ }
19
+ function humaniseError(row) {
20
+ return row.errors.map(humaniseReason).join('\n');
17
21
  }
18
22
  function stringifyLabels(labels) {
19
23
  return labels.map(label => (typeof label === 'string' ? label : label.name)).join(',');
@@ -40,7 +44,7 @@ function toErrorReportRow(row) {
40
44
  labels: stringifyLabels(row.labels),
41
45
  tiers: '',
42
46
  gift_id: row.gift_id || null,
43
- error: humaniseError(row.error)
47
+ error: humaniseError(row)
44
48
  };
45
49
  }
46
50
  // The error report attached to the completion email: the failed rows as CSV. It shares
@@ -34,14 +34,19 @@ interface EmailPayload {
34
34
 
35
35
  // Turn an ORM validation error into copy a member manager can act on. Presentation
36
36
  // only: the API response keeps the raw messages, and just the emailed report is
37
- // rewritten.
38
- function humaniseError(message: string): string {
37
+ // rewritten. Takes one reason at a time, so a rewrite matching to the end of its input
38
+ // cannot consume the reason after it.
39
+ function humaniseReason(message: string): string {
39
40
  return message
40
41
  .replace('Value in [members.email] cannot be blank.', 'Missing email address')
41
42
  .replace('Value in [members.note] exceeds maximum length of 2000 characters.', '"Note" exceeds maximum length of 2000 characters')
42
43
  .replace('Value in [members.subscribed] must be one of true, false, 0 or 1.', 'Value in "Subscribed to emails" must be "true" or "false"')
43
44
  .replace('Validation (isEmail) failed for email', 'Invalid email address')
44
- .replace(/No such customer:[^,]*/, 'Could not find Stripe customer');
45
+ .replace(/No such customer:[\s\S]*/, 'Could not find Stripe customer');
46
+ }
47
+
48
+ function humaniseError(row: ImportErrorRow): string {
49
+ return row.errors.map(humaniseReason).join('\n');
45
50
  }
46
51
 
47
52
  // One row of the fixed, member-vocabulary part of the error report, in emit order.
@@ -93,7 +98,7 @@ function toErrorReportRow(row: ImportErrorRow): ErrorReportRow {
93
98
  labels: stringifyLabels(row.labels),
94
99
  tiers: '',
95
100
  gift_id: row.gift_id || null,
96
- error: humaniseError(row.error)
101
+ error: humaniseError(row)
97
102
  };
98
103
  }
99
104
 
@@ -165,7 +165,7 @@ class MembersCSVImporter {
165
165
  // before any member write -- so there is no reason to hold a transaction
166
166
  // across it, and doing so would deadlock the single-connection SQLite pool.
167
167
  const customFieldPlan = activeCustomFields.length > 0
168
- ? await this._customFields.planWrite((0, csv_2.fieldValuesFromCsvRow)(activeCustomFields, row, csv_1.stripFormulaGuard))
168
+ ? await namingTheColumn(() => this._customFields.planWrite((0, csv_2.fieldValuesFromCsvRow)(activeCustomFields, row, csv_1.stripFormulaGuard)))
169
169
  : [];
170
170
  trx = await this._knex.transaction(undefined, { doNotRejectOnRollback: false });
171
171
  const options = { transacting: trx, context: IMPORT_CONTEXT };
@@ -265,15 +265,16 @@ class MembersCSVImporter {
265
265
  }
266
266
  catch (error) {
267
267
  const errorList = Array.isArray(error) ? error : [error];
268
- const errorMessage = errorList
268
+ const reasons = errorList
269
269
  .map(e => (typeof e === 'object' && e !== null && 'message' in e ? e.message : undefined))
270
- .join(', ');
270
+ .filter((message) => typeof message === 'string');
271
+ const errorMessage = reasons.join('\n');
271
272
  // trx is unset if the row failed before the transaction opened (a bad
272
273
  // custom field value or gift combination).
273
274
  if (trx) {
274
275
  await trx.rollback();
275
276
  }
276
- importErrors.push({ ...row, error: errorMessage });
277
+ importErrors.push({ ...row, error: errorMessage, errors: reasons });
277
278
  }
278
279
  }
279
280
  await Promise.all(archivableStripePriceIds.map(id => this._stripe.archivePrice(id)));
@@ -287,6 +288,26 @@ class MembersCSVImporter {
287
288
  return { imported: 0, errors: importErrors };
288
289
  }
289
290
  }
291
+ // A row error is read next to a spreadsheet, and a value rejection states only what the
292
+ // value should be — the same sentence Admin shows under a single input, where the field is
293
+ // already on screen. `property` is the dotted path a default export writes as its column
294
+ // header, so prefixing with it names the column a publisher has to go and fix.
295
+ //
296
+ // A rejection about the whole request carries the bare namespace and names no field after
297
+ // it, so it is left alone rather than made to point at a column that does not exist.
298
+ async function namingTheColumn(plan) {
299
+ try {
300
+ return await plan();
301
+ }
302
+ catch (error) {
303
+ const { property, message } = (error ?? {});
304
+ const namesAField = typeof property === 'string' && property.includes('.');
305
+ if (!namesAField || typeof message !== 'string') {
306
+ throw error;
307
+ }
308
+ throw new errors.DataImportError({ message: `${property}: ${message}` });
309
+ }
310
+ }
290
311
  function wrapGiftError(error) {
291
312
  const err = error;
292
313
  const message = (err && typeof err.message === 'string' && err.message) || tpl(messages.giftReassignFailed);