ghost 6.56.0 → 6.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) 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-adapter-base-scheduling-0.2.1.tgz +0 -0
  5. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  6. package/components/tryghost-kg-converters-1.2.4.tgz +0 -0
  7. package/components/tryghost-kg-default-cards-10.3.4.tgz +0 -0
  8. package/components/tryghost-kg-default-nodes-2.1.5.tgz +0 -0
  9. package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
  10. package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
  11. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  12. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  13. package/core/app.js +1 -1
  14. package/core/boot.js +21 -25
  15. package/core/bridge.js +10 -12
  16. package/core/built/admin/assets/Combination-BDvCdg1t.js +41 -0
  17. package/core/built/admin/assets/about-BNojRaxf.js +1 -0
  18. package/core/built/admin/assets/{account-migration-D3O_yVck.js → account-migration-DMS8JsM-.js} +1 -1
  19. package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
  20. package/core/built/admin/assets/activitypub/{account-migration-D8FbP-Z-.js → account-migration-DHFbePl6.js} +7 -7
  21. package/core/built/admin/assets/activitypub/activitypub.js +3 -3
  22. package/core/built/admin/assets/activitypub/{avatar-11EA1VSs.js → avatar-CT4tS2_S.js} +2 -2
  23. package/core/built/admin/assets/activitypub/{avatar-flipboard-jfbLG-xe.js → avatar-flipboard-ByYWKipo.js} +2 -2
  24. package/core/built/admin/assets/activitypub/{bluesky-sharing-f1jTQe2P.js → bluesky-sharing-srVnSHDz.js} +8 -8
  25. package/core/built/admin/assets/activitypub/{content-formatters-nmTOPY9M.js → content-formatters-C2sos5Ht.js} +3 -3
  26. package/core/built/admin/assets/activitypub/{copy-BCwo9ep3.js → copy-B1UBTOoZ.js} +2 -2
  27. package/core/built/admin/assets/activitypub/{dist-BEfX-0aW.js → dist-MPsb2WZ1.js} +4 -4
  28. package/core/built/admin/assets/activitypub/{domain-AaDlZlrA.js → domain-DzcM9Dzo.js} +9 -9
  29. package/core/built/admin/assets/activitypub/{edit-profile-DPi8Tl5M.js → edit-profile-CsQ_4xAk.js} +5 -5
  30. package/core/built/admin/assets/activitypub/{explore-CxTzjILQ.js → explore-CJe0DZBK.js} +8 -8
  31. package/core/built/admin/assets/activitypub/{feed-DPv5x2MX.js → feed-CKV3qWON.js} +9 -9
  32. package/core/built/admin/assets/activitypub/{field-DbgA8lat.js → field-DckHfDJf.js} +3 -3
  33. package/core/built/admin/assets/activitypub/{hash-BWuZoYkA.js → hash-Dlp8RM55.js} +2 -2
  34. package/core/built/admin/assets/activitypub/{inbox-35VDQ2K2.js → inbox-CsPy4R0M.js} +2 -2
  35. package/core/built/admin/assets/activitypub/{inbox-DSrF_i1O.js → inbox-DAsjzUUe.js} +10 -10
  36. package/core/built/admin/assets/activitypub/{label-CPjFBbo7.js → label-DWyo9E5T.js} +3 -3
  37. package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +1 -0
  38. package/core/built/admin/assets/activitypub/{moderation-BkDC1XVZ.js → moderation-B0Inc03M.js} +7 -7
  39. package/core/built/admin/assets/activitypub/{note-xEyH4xT-.js → note-CSE54lER.js} +7 -7
  40. package/core/built/admin/assets/activitypub/{notifications-DClaL3ui.js → notifications-RSwbKqdh.js} +9 -9
  41. package/core/built/admin/assets/activitypub/{preferences-DpnOa-4Z.js → preferences-CE231n2_.js} +11 -11
  42. package/core/built/admin/assets/activitypub/{profile-Dvi9mz-6.js → profile-aaQ1j1Eo.js} +13 -13
  43. package/core/built/admin/assets/activitypub/{reply-Cs9ySd6Z.js → reply-Cjb0Vs3m.js} +2 -2
  44. package/core/built/admin/assets/activitypub/{routes-DFBeTnfn.js → routes-Djb0mc9k.js} +26 -26
  45. package/core/built/admin/assets/activitypub/{separator-C5u_VjzY.js → separator-D8pROI5n.js} +3 -3
  46. package/core/built/admin/assets/activitypub/{settings-CV2fFu6D.js → settings-DMc32IQl.js} +5 -5
  47. package/core/built/admin/assets/activitypub/{site-_TgDd_EM.js → site-DVIcsohX.js} +4 -4
  48. package/core/built/admin/assets/activitypub/{step-1-BfnNL2oF.js → step-1-9Yey9usF.js} +5 -5
  49. package/core/built/admin/assets/activitypub/{step-2-DLndZkO-.js → step-2-DdYmKRXV.js} +6 -6
  50. package/core/built/admin/assets/activitypub/{step-3-A-PZTtRE.js → step-3-hdSkcjZU.js} +10 -10
  51. package/core/built/admin/assets/activitypub/{tabs-BUbz7tea.js → tabs-CUqseYzQ.js} +6 -6
  52. package/core/built/admin/assets/activitypub/{topic-filter-BdX_xYYT.js → topic-filter-Coni6OXM.js} +3 -3
  53. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-EwuRemHh.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
  54. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-cy4ACBc4.js → use-navigate-with-base-path-B046AM38.js} +24 -7
  55. package/core/built/admin/assets/activitypub/{use-reply-chain-data-CyFcGfan.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
  56. package/core/built/admin/assets/activitypub/{x-7zk8mV2p.js → x-BRfRYd16.js} +2 -2
  57. package/core/built/admin/assets/add-integration-modal-DU9ghGLS.js +1 -0
  58. package/core/built/admin/assets/add-newsletter-modal-DFxuQrYu.js +1 -0
  59. package/core/built/admin/assets/add-recommendation-modal-D-3LOrdd.js +1 -0
  60. package/core/built/admin/assets/analytics-DTIMRhWw.js +1 -0
  61. package/core/built/admin/assets/announcement-bar-modal-DGCcezB6.js +1 -0
  62. package/core/built/admin/assets/api-keys-Dx4NeNpK.js +1 -0
  63. package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
  64. package/core/built/admin/assets/{audience-CkBb2LlO.js → audience-DkknNqhZ.js} +1 -1
  65. package/core/built/admin/assets/automated-emails-PayPCT8L.js +1 -0
  66. package/core/built/admin/assets/{automations-DhKeS_U5.js → automations-162q5HjL.js} +1 -1
  67. package/core/built/admin/assets/{automations-cMllU545.js → automations-DvSU7QhW.js} +1 -1
  68. package/core/built/admin/assets/{bluesky-sharing-DPGpyD-G.js → bluesky-sharing-yZ9A_jL8.js} +1 -1
  69. package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
  70. package/core/built/admin/assets/{breadcrumb-DIfcF5ow.js → breadcrumb-CjOH7LCU.js} +1 -1
  71. package/core/built/admin/assets/{chart-helpers-DV0FkdcD.js → chart-helpers-DGxvWnBT.js} +1 -1
  72. package/core/built/admin/assets/checkbox-BSUcnNq_.js +1 -0
  73. package/core/built/admin/assets/{chunk.332.05991a101d67f732dec3.js → chunk.424.4a27628efdade15c2dab.js} +28 -27
  74. package/core/built/admin/assets/chunk.524.dce1c51e0bc30fb2718e.js +25 -0
  75. package/core/built/admin/assets/{chunk.211.6f7328d63c41536451b3.js → chunk.571.40f093ed6d883993714e.js} +5 -5
  76. package/core/built/admin/assets/chunk.582.3a430d5c6a744fd475b6.js +27 -0
  77. package/core/built/admin/assets/code-editor-view-BcyKABrG.js +1 -0
  78. package/core/built/admin/assets/color-picker-field-8NoSnhNI.js +1 -0
  79. package/core/built/admin/assets/color-utils-Dzeeuj1F.js +1 -0
  80. package/core/built/admin/assets/combobox-5rdgDrDV.js +1 -0
  81. package/core/built/admin/assets/command-BOYMbZa_.js +1 -0
  82. package/core/built/admin/assets/comments-BhNM5CYl.js +1 -0
  83. package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
  84. package/core/built/admin/assets/confirmation-modal-CYD7x3UQ.js +1 -0
  85. package/core/built/admin/assets/content-api-modal-BtW4Z1-1.js +1 -0
  86. package/core/built/admin/assets/copy-field-CEp_XieW.js +1 -0
  87. package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
  88. package/core/built/admin/assets/custom-integration-modal-bk3lNEuQ.js +1 -0
  89. package/core/built/admin/assets/{date-range-select-dXc-GsVe.js → date-range-select-B3Ij1Rqg.js} +1 -1
  90. package/core/built/admin/assets/debounce-Cx5B_9OI.js +3 -0
  91. package/core/built/admin/assets/design-and-theme-modal-B2xYKS1I.js +23 -0
  92. package/core/built/admin/assets/dirty-confirm-dialog-YE9L21gY.js +1 -0
  93. package/core/built/admin/assets/disabled-sources-indicator-uIzbWL7K.js +1 -0
  94. package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-ARN6XTcR.js} +1 -1
  95. package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
  96. package/core/built/admin/assets/{dist-DyViJwIy.js → dist-Bg8bSvLA.js} +3 -3
  97. package/core/built/admin/assets/dist-CsvXCJSe.js +23 -0
  98. package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
  99. package/core/built/admin/assets/dist-j1xxxnY8.js +1 -0
  100. package/core/built/admin/assets/dist-myQaY0ka.js +1 -0
  101. package/core/built/admin/assets/{domain-B5ogXUBa.js → domain-DzrOAPY8.js} +1 -1
  102. package/core/built/admin/assets/{dropzone-BnFG7dDP.js → dropzone-CzhP33wa.js} +1 -1
  103. package/core/built/admin/assets/{edit-profile-B07jCZy9.js → edit-profile-oufs4R4n.js} +1 -1
  104. package/core/built/admin/assets/editor-BBKrunMK.js +8 -0
  105. package/core/built/admin/assets/embed-signup-form-modal-Ch3s6lqA.js +9 -0
  106. package/core/built/admin/assets/{explore-Ca1y8HW_.js → explore-CA-R01Br.js} +1 -1
  107. package/core/built/admin/assets/{feed-x4iFMBP4.js → feed-ClC8_Gld.js} +1 -1
  108. package/core/built/admin/assets/{field-ChCAabZg.js → field-B3OPenbP.js} +1 -1
  109. package/core/built/admin/assets/{filter-sources-LdtH1e-T.js → filter-sources-CiPLnG9A.js} +1 -1
  110. package/core/built/admin/assets/filters-Bz0HsRie.js +1 -0
  111. package/core/built/admin/assets/{filters-CjVKRP8U.js → filters-yFvybRMe.js} +1 -1
  112. package/core/built/admin/assets/first-promoter-modal-BC0UpYCD.js +1 -0
  113. package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
  114. package/core/built/admin/assets/{ghost-7f01131115186d1af84ead76e79646dd.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +300 -298
  115. package/core/built/admin/assets/{gift-link-modal-Buqh2vXh.js → gift-link-modal-BKRuqo-k.js} +1 -1
  116. package/core/built/admin/assets/{gift-links-CsUhefWG.js → gift-links-CVuqLhsZ.js} +1 -1
  117. package/core/built/admin/assets/global-data-provider-RzW6Bnlf.js +1 -0
  118. package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
  119. package/core/built/admin/assets/{growth-DajOBdN0.js → growth-ClNAldfL.js} +1 -1
  120. package/core/built/admin/assets/{growth-Dy2UOSay.js → growth-Dz8OSG06.js} +2 -2
  121. package/core/built/admin/assets/history-modal-DIVuQ5Ct.js +1 -0
  122. package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
  123. package/core/built/admin/assets/html-field-CVZ2sMxw.js +1 -0
  124. package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
  125. package/core/built/admin/assets/images-Ok8FFJ3A.js +1 -0
  126. package/core/built/admin/assets/{inbox-BUOLDoji.js → inbox-CMUVI6qb.js} +13 -13
  127. package/core/built/admin/assets/index-DUXM0NlS.js +67 -0
  128. package/core/built/admin/assets/index-Ds2iUoF6.css +1 -0
  129. package/core/built/admin/assets/index.es-B9tvf-t4.js +1 -0
  130. package/core/built/admin/assets/{input-group-uSK3XT7b.js → input-group-2XU-iuh_.js} +1 -1
  131. package/core/built/admin/assets/integration-header-B4DyXV06.js +1 -0
  132. package/core/built/admin/assets/integrations-BT6_iJvu.js +1 -0
  133. package/core/built/admin/assets/invite-user-modal-DrZ7xxrZ.js +1 -0
  134. package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
  135. package/core/built/admin/assets/koenig-editor-base-BPj4S760.js +1 -0
  136. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9254 -9251
  137. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
  138. package/core/built/admin/assets/{koenig-lexical-BFc4yFed.js → koenig-lexical-Cdjv-m3_.js} +98 -98
  139. package/core/built/admin/assets/{kpi-card-CCt-qeWD.js → kpi-card-J8Mj1sDX.js} +1 -1
  140. package/core/built/admin/assets/{kpi-card-BN-E6WYX.js → kpi-card-awos0BMH.js} +1 -1
  141. package/core/built/admin/assets/{kpi-helpers-DlxTbTs4.js → kpi-helpers-SEhvH-8E.js} +1 -1
  142. package/core/built/admin/assets/{kpi-tabs-BlcUttpn.js → kpi-tabs-R6BjsKhd.js} +1 -1
  143. package/core/built/admin/assets/{label-AVrxwmsz.js → label-BMC3dzLI.js} +1 -1
  144. package/core/built/admin/assets/label-picker-Bnlru5tO.js +1 -0
  145. package/core/built/admin/assets/{labels-DAIqKed7.js → labels-DAPjKPy2.js} +1 -1
  146. package/core/built/admin/assets/layout-yR9rlvEw.js +1 -0
  147. package/core/built/admin/assets/limit-modal-COmLevuY.js +1 -0
  148. package/core/built/admin/assets/{link-helpers-D9LsDOkG.js → link-helpers-Bzkk68GD.js} +1 -1
  149. package/core/built/admin/assets/member-custom-fields-BvFKEpxJ.js +1 -0
  150. package/core/built/admin/assets/member-detail-CWdPAe3p.js +1 -0
  151. package/core/built/admin/assets/members-Bnt0ih2R.js +11 -0
  152. package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
  153. package/core/built/admin/assets/{moderation-CQ3urTFE.js → moderation-mPjYHgZW.js} +1 -1
  154. package/core/built/admin/assets/multi-select-combobox-CsoyHAiX.js +1 -0
  155. package/core/built/admin/assets/navigation-modal-D_c0badj.js +1 -0
  156. package/core/built/admin/assets/newsletter-DR49NyGS.js +1 -0
  157. package/core/built/admin/assets/newsletter-detail-modal-B45pL1pb.js +1 -0
  158. package/core/built/admin/assets/newsletter-emails-B-OLfrvr.js +1 -0
  159. package/core/built/admin/assets/{newsletters-vt3Ni1Ov.js → newsletters-DGs5BaKH.js} +1 -1
  160. package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
  161. package/core/built/admin/assets/{note-Dk877BTv.js → note-CSVsUKBm.js} +1 -1
  162. package/core/built/admin/assets/{notifications-CPlLjPxO.js → notifications-DV9FRtDw.js} +1 -1
  163. package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
  164. package/core/built/admin/assets/offers-container-modal-B0hFS8nk.js +1 -0
  165. package/core/built/admin/assets/{onboarding-route-DdStOtjy.js → onboarding-route--jL5KLPD.js} +1 -1
  166. package/core/built/admin/assets/overview-B-ZPNqMo.js +1 -0
  167. package/core/built/admin/assets/overview-D96iwoJi.js +1 -0
  168. package/core/built/admin/assets/{page-header-pp4dWmOS.js → page-header-DAvDYKO9.js} +1 -1
  169. package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
  170. package/core/built/admin/assets/pintura-modal-jiUCxX0Q.js +1 -0
  171. package/core/built/admin/assets/portal-frame-D4D4nEO6.js +1 -0
  172. package/core/built/admin/assets/portal-modal-BLFdYeDH.js +1 -0
  173. package/core/built/admin/assets/{post-analytics-BMJrI81R.js → post-analytics-BhAtiFJp.js} +1 -1
  174. package/core/built/admin/assets/post-analytics-header-DK27VjJm.js +1 -0
  175. package/core/built/admin/assets/{post-analytics-provider-Btml5vJI.js → post-analytics-provider-DGg0FUqs.js} +1 -1
  176. package/core/built/admin/assets/{post-share-modal-AcUZkbfL.js → post-share-modal-DtFAC132.js} +1 -1
  177. package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
  178. package/core/built/admin/assets/preferences-CuWLISHa.js +5 -0
  179. package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
  180. package/core/built/admin/assets/preview-modal-DctYx20m.js +1 -0
  181. package/core/built/admin/assets/profile-BsKYYtuL.js +1 -0
  182. package/core/built/admin/assets/radio-group-CI5_Kf_d.js +1 -0
  183. package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
  184. package/core/built/admin/assets/{router-provider-BmEZf6c6.js → router-provider-DSQfprxH.js} +1 -1
  185. package/core/built/admin/assets/select-CkXqNJDJ.js +1 -0
  186. package/core/built/admin/assets/settings-B4vM-rgV.js +4 -0
  187. package/core/built/admin/assets/{settings-BGgk7oCI.js → settings-ZCj-W56o.js} +1 -1
  188. package/core/built/admin/assets/settings-breadcrumbs-DIq31FwA.js +1 -0
  189. package/core/built/admin/assets/{share-modal-BeuydaBf.js → share-modal-BNj14g79.js} +1 -1
  190. package/core/built/admin/assets/slack-modal-C6SpMQ-q.js +1 -0
  191. package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
  192. package/core/built/admin/assets/{sort-button-Df4t2AL8.js → sort-button-B-k54ow2.js} +1 -1
  193. package/core/built/admin/assets/{source-icon-ajoaKb41.js → source-icon-Cej3sZM6.js} +1 -1
  194. package/core/built/admin/assets/{stats-C_lfxZAw.js → stats-B2KW8E2y.js} +1 -1
  195. package/core/built/admin/assets/{stats-view-Cxot1iD8.js → stats-view-CVzU2-p5.js} +1 -1
  196. package/core/built/admin/assets/{step-1-ogAA4lDr.js → step-1-Dbn1iIMU.js} +1 -1
  197. package/core/built/admin/assets/{step-2-C5Xini-7.js → step-2-BahnSuN6.js} +1 -1
  198. package/core/built/admin/assets/{step-3-CZv1FV81.js → step-3-CW04e2ZJ.js} +2 -2
  199. package/core/built/admin/assets/stripe-button-BkBZqaGG.js +1 -0
  200. package/core/built/admin/assets/stripe-connect-modal-CYDDrSJm.js +1 -0
  201. package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
  202. package/core/built/admin/assets/{table-DFj1wiYv.js → table-BtLcEi1M.js} +1 -1
  203. package/core/built/admin/assets/tabs-BaWRfg8j.js +1 -0
  204. package/core/built/admin/assets/tag-detail-CmqiC32L.js +1 -0
  205. package/core/built/admin/assets/tags-BbygUvNB.js +1 -0
  206. package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
  207. package/core/built/admin/assets/testimonials-modal--bdzppyh.js +1 -0
  208. package/core/built/admin/assets/tier-detail-modal-BZmAsIy4.js +1 -0
  209. package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
  210. package/core/built/admin/assets/toggle-group-CVl-Xigr.js +1 -0
  211. package/core/built/admin/assets/{topic-filter-DGDxDalo.js → topic-filter-BoRHRbL1.js} +1 -1
  212. package/core/built/admin/assets/transistor-modal-CBPDqEGy.js +1 -0
  213. package/core/built/admin/assets/unsplash-modal-CixoRAO2.js +1 -0
  214. package/core/built/admin/assets/{use-analytics-data-C76v4LIp.js → use-analytics-data-BiWvcf1u.js} +1 -1
  215. package/core/built/admin/assets/use-check-theme-limit-error-B14lZukR.js +2 -0
  216. package/core/built/admin/assets/use-feature-flag-BmJoc5WN.js +1 -0
  217. package/core/built/admin/assets/use-filter-params-ChoV72Hw.js +1 -0
  218. package/core/built/admin/assets/use-filterable-api-iMpDZBxD.js +1 -0
  219. package/core/built/admin/assets/use-form-vRjI0Kwk.js +1 -0
  220. package/core/built/admin/assets/{use-growth-stats-aNXJOmQr.js → use-growth-stats-Dom8xVzo.js} +1 -1
  221. package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
  222. package/core/built/admin/assets/use-limiter-Dwg8ivgQ.js +2 -0
  223. package/core/built/admin/assets/use-pintura-config-Bj4QmiVk.js +1 -0
  224. package/core/built/admin/assets/use-pintura-editor-DvrslmPE.js +1 -0
  225. package/core/built/admin/assets/{use-post-referrers-CG0SZioj.js → use-post-referrers-pxIHTXFs.js} +1 -1
  226. package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
  227. package/core/built/admin/assets/{use-reply-chain-data-RLdPKRmS.js → use-reply-chain-data-CmFe0N6F.js} +1 -1
  228. package/core/built/admin/assets/use-setting-group-Cmp_sy-d.js +1 -0
  229. package/core/built/admin/assets/use-settings-navigation-BmROPrls.js +1 -0
  230. package/core/built/admin/assets/{use-simple-pagination-DgcF69KI.js → use-simple-pagination-D5rL3YwO.js} +1 -1
  231. package/core/built/admin/assets/{use-tinybird-query-C1R_Kbmy.js → use-tinybird-query-DP2dMaq7.js} +1 -1
  232. package/core/built/admin/assets/{use-tinybird-token-C9fPjVM_.js → use-tinybird-token-DrK_skPQ.js} +1 -1
  233. package/core/built/admin/assets/use-url-input-ChrUZBFf.js +5 -0
  234. package/core/built/admin/assets/user-detail-modal-XODpOqNC.js +1 -0
  235. package/core/built/admin/assets/{users-Dkv8NQZe.js → users-B6hIds6n.js} +1 -1
  236. package/core/built/admin/assets/validator-Cr0sMKuW.js +7 -0
  237. package/core/built/admin/assets/{virtual-list-BmTUzeoF.js → virtual-list-Dnoafqbk.js} +1 -1
  238. package/core/built/admin/assets/web-D71mjB2r.js +1 -0
  239. package/core/built/admin/assets/web-z71lA1Y3.js +2 -0
  240. package/core/built/admin/assets/zapier-modal-Bp507Um7.js +1 -0
  241. package/core/built/admin/index.html +16 -14
  242. package/core/frontend/helpers/authors.js +1 -1
  243. package/core/frontend/helpers/link.js +3 -8
  244. package/core/frontend/helpers/tags.js +1 -1
  245. package/core/frontend/meta/author-url.js +2 -2
  246. package/core/frontend/meta/url.js +4 -4
  247. package/core/frontend/services/proxy.js +1 -1
  248. package/core/frontend/services/routing/events.js +20 -6
  249. package/core/frontend/services/routing/permalink-adapter.js +0 -13
  250. package/core/frontend/services/routing/permalink-adapter.ts +0 -13
  251. package/core/frontend/services/routing/router-manager.js +15 -9
  252. package/core/frontend/services/sitemap/base-site-map-generator.js +1 -28
  253. package/core/frontend/services/sitemap/site-map-manager.js +22 -57
  254. package/core/frontend/web/middleware/admin-toolbar.js +1 -1
  255. package/core/frontend/web/middleware/serve-indexnow-key.js +1 -7
  256. package/core/server/adapters/route-settings/utils.js +10 -2
  257. package/core/server/adapters/route-settings/utils.ts +12 -2
  258. package/core/server/api/endpoints/email-post.js +1 -1
  259. package/core/server/api/endpoints/index.js +2 -2
  260. package/core/server/api/endpoints/previews.js +1 -1
  261. package/core/server/api/endpoints/search-index-public.js +3 -3
  262. package/core/server/api/endpoints/search-index.js +4 -4
  263. package/core/server/api/endpoints/themes.js +6 -1
  264. package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -2
  265. package/core/server/api/endpoints/utils/serializers/input/comments.js +1 -1
  266. package/core/server/api/endpoints/utils/serializers/input/pages.js +3 -3
  267. package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
  268. package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
  269. package/core/server/api/endpoints/utils/serializers/input/users.js +2 -2
  270. package/core/server/api/endpoints/utils/serializers/input/utils/url.js +31 -13
  271. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +2 -2
  272. package/core/server/api/endpoints/utils/serializers/output/mappers/tags.js +2 -2
  273. package/core/server/api/endpoints/utils/serializers/output/mappers/users.js +8 -0
  274. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +18 -11
  275. package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +74 -0
  276. package/core/server/data/migrations/versions/6.57/2026-08-04-18-54-14-reset-automation-email-analytics.js +25 -0
  277. package/core/server/data/schema/commands.js +21 -5
  278. package/core/server/data/schema/schema.js +24 -5
  279. package/core/server/lib/common/to-plain.ts +1 -1
  280. package/core/server/models/base/plugins/overrides.js +7 -5
  281. package/core/server/models/post.js +2 -2
  282. package/core/server/services/adapter-manager/adapter-manager.js +1 -45
  283. package/core/server/services/adapter-manager/adapter-manager.ts +2 -48
  284. package/core/server/services/adapter-manager/adapter-paths.js +28 -0
  285. package/core/server/services/adapter-manager/adapter-paths.ts +26 -0
  286. package/core/server/services/adapter-manager/base-classes.js +22 -0
  287. package/core/server/services/adapter-manager/base-classes.ts +22 -0
  288. package/core/server/services/adapter-manager/index.js +4 -27
  289. package/core/server/services/adapter-manager/index.ts +4 -31
  290. package/core/server/services/adapter-manager/utils.js +50 -0
  291. package/core/server/services/adapter-manager/utils.ts +57 -0
  292. package/core/server/services/audience-feedback/audience-feedback-controller.js +11 -2
  293. package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
  294. package/core/server/services/audience-feedback/feedback-repository.js +4 -1
  295. package/core/server/services/auth/session/session-service.js +18 -3
  296. package/core/server/services/automations/database-automations-repository.js +5 -5
  297. package/core/server/services/automations/database-automations-repository.ts +12 -12
  298. package/core/server/services/automations/database-date.js +52 -3
  299. package/core/server/services/automations/database-date.ts +23 -2
  300. package/core/server/services/automations/service.js +2 -2
  301. package/core/server/services/automations/service.ts +2 -2
  302. package/core/server/services/comments/comments-service-emails.js +1 -1
  303. package/core/server/services/comments/comments-service.js +1 -1
  304. package/core/server/services/email-analytics/email-analytics-service-wrapper.js +12 -3
  305. package/core/server/services/email-analytics/email-analytics-service.js +4 -3
  306. package/core/server/services/email-analytics/index.js +2 -3
  307. package/core/server/services/email-analytics/index.ts +1 -2
  308. package/core/server/services/email-analytics/lib/queries.js +91 -124
  309. package/core/server/services/email-analytics/lib/queries.ts +341 -0
  310. package/core/server/services/email-service/email-service-wrapper.js +2 -1
  311. package/core/server/services/email-service/mailgun-email-provider.js +15 -0
  312. package/core/server/services/indexnow-ping/index.js +0 -2
  313. package/core/server/services/indexnow-ping/index.ts +0 -2
  314. package/core/server/services/indexnow-ping/indexnow-ping-service.js +6 -8
  315. package/core/server/services/indexnow-ping/indexnow-ping-service.ts +9 -17
  316. package/core/server/services/lib/mailgun-client.js +3 -3
  317. package/core/server/services/media-inliner/external-media-inliner.js +12 -8
  318. package/core/server/services/member-attribution/url-translator.js +5 -7
  319. package/core/server/services/member-welcome-emails/service.js +72 -12
  320. package/core/server/services/members/api.js +1 -1
  321. package/core/server/services/members/import-export/import/completion-email.js +8 -4
  322. package/core/server/services/members/import-export/import/completion-email.ts +9 -4
  323. package/core/server/services/members/import-export/import/importer.js +25 -4
  324. package/core/server/services/members/import-export/import/importer.ts +26 -6
  325. package/core/server/services/members/import-export/import/row.ts +5 -1
  326. package/core/server/services/members/import-export/index.js +1 -5
  327. package/core/server/services/members/import-export/index.ts +2 -6
  328. package/core/server/services/members/members-api/repositories/member-repository.js +51 -0
  329. package/core/server/services/members-custom-fields/queries.js +4 -13
  330. package/core/server/services/members-custom-fields/queries.ts +4 -13
  331. package/core/server/services/members-custom-fields/schema.js +18 -27
  332. package/core/server/services/members-custom-fields/schema.ts +17 -27
  333. package/core/server/services/members-custom-fields/storage.js +94 -27
  334. package/core/server/services/members-custom-fields/storage.ts +114 -39
  335. package/core/server/services/members-custom-fields/values-service.js +98 -219
  336. package/core/server/services/members-custom-fields/values-service.ts +110 -238
  337. package/core/server/services/mentions/resource-service.js +1 -1
  338. package/core/server/services/mentions/service.js +2 -2
  339. package/core/server/services/route-settings/dynamic-routing-service.js +9 -59
  340. package/core/server/services/slack-notifications/slack-notifications.js +2 -2
  341. package/core/server/services/slack-ping/index.js +1 -1
  342. package/core/server/services/slack-ping/index.ts +1 -1
  343. package/core/server/services/slack-ping/slack-ping-service.js +3 -2
  344. package/core/server/services/slack-ping/slack-ping-service.ts +4 -5
  345. package/core/server/services/stats/content-stats-service.js +3 -3
  346. package/core/server/services/stats/members-stats-service.js +0 -7
  347. package/core/server/services/stats/posts-stats-service.js +2 -2
  348. package/core/server/services/themes/storage.js +19 -2
  349. package/core/server/services/url/config.js +90 -135
  350. package/core/server/services/url/index.js +6 -49
  351. package/core/server/services/url/lazy-find-resource.js +14 -11
  352. package/core/server/services/url/lazy-find-resource.ts +14 -11
  353. package/core/server/services/url/lazy-url-service.js +174 -94
  354. package/core/server/services/url/lazy-url-service.ts +233 -97
  355. package/core/server/services/url/routable-resources.js +62 -71
  356. package/core/server/services/url/router-filter.js +12 -4
  357. package/core/server/services/url/router-filter.ts +12 -4
  358. package/core/server/services/webhooks/index.js +1 -1
  359. package/core/server/services/webhooks/serialize.js +12 -2
  360. package/core/server/web/api/endpoints/admin/routes.js +2 -2
  361. package/core/shared/config/env/config.testing-mysql.json +1 -2
  362. package/core/shared/config/env/config.testing.json +1 -2
  363. package/core/shared/custom-theme-settings-cache/custom-theme-settings-bread-service.js +4 -0
  364. package/core/shared/custom-theme-settings-cache/custom-theme-settings-service.js +39 -0
  365. package/core/shared/events/index.js +1 -2
  366. package/core/shared/labs.js +4 -4
  367. package/package.json +10 -11
  368. package/pnpm-lock.yaml +44 -47
  369. package/pnpm-workspace.yaml +19 -19
  370. package/tsconfig.json +1 -0
  371. package/components/tryghost-adapter-base-scheduling-0.2.0.tgz +0 -0
  372. package/components/tryghost-kg-converters-1.2.3.tgz +0 -0
  373. package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
  374. package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
  375. package/core/built/admin/assets/Combination-BCm9FL4o.js +0 -41
  376. package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +0 -1
  377. package/core/built/admin/assets/automated-emails-6t2bIRWf.js +0 -7
  378. package/core/built/admin/assets/checkbox-BIl077a0.js +0 -1
  379. package/core/built/admin/assets/chunk.524.87b52f4fada529df2c4c.js +0 -25
  380. package/core/built/admin/assets/chunk.582.340bcca873e806cbd4bb.js +0 -27
  381. package/core/built/admin/assets/code-editor-view-C0aWNwdl.js +0 -1
  382. package/core/built/admin/assets/comments-qwhIjH3c.js +0 -1
  383. package/core/built/admin/assets/disabled-sources-indicator-DYAXW-6V.js +0 -1
  384. package/core/built/admin/assets/dist-BUKmD51u.js +0 -1
  385. package/core/built/admin/assets/dist-CNlMj7ML.js +0 -1
  386. package/core/built/admin/assets/dist-DDY6GRVz.js +0 -1
  387. package/core/built/admin/assets/dist-DG78yroX.js +0 -23
  388. package/core/built/admin/assets/editor-DDYSOXGN.js +0 -8
  389. package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
  390. package/core/built/admin/assets/filters-CLehPo_D.js +0 -1
  391. package/core/built/admin/assets/index-08n05-rc.js +0 -67
  392. package/core/built/admin/assets/index-DgUzgFvS.css +0 -1
  393. package/core/built/admin/assets/label-picker-DVnpu_IF.js +0 -1
  394. package/core/built/admin/assets/layout-B0YLh7R9.js +0 -1
  395. package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +0 -1
  396. package/core/built/admin/assets/member-detail-CIVAdKXf.js +0 -1
  397. package/core/built/admin/assets/members-oLPiSkXD.js +0 -10
  398. package/core/built/admin/assets/modals-CB7v-k3T.js +0 -31
  399. package/core/built/admin/assets/newsletter-rbeDX9Wk.js +0 -1
  400. package/core/built/admin/assets/overview--4V-kx9c.js +0 -1
  401. package/core/built/admin/assets/overview-DSLv30oz.js +0 -1
  402. package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +0 -1
  403. package/core/built/admin/assets/preferences-Ru4CRPHC.js +0 -5
  404. package/core/built/admin/assets/profile-BNb1oj7D.js +0 -1
  405. package/core/built/admin/assets/select-B0HteHrp.js +0 -1
  406. package/core/built/admin/assets/settings-B1C9Q3wo.js +0 -4
  407. package/core/built/admin/assets/social-urls-DA2lXrhW.js +0 -8
  408. package/core/built/admin/assets/switch-CyQcwMAv.js +0 -1
  409. package/core/built/admin/assets/tabs-D4y1gqUd.js +0 -1
  410. package/core/built/admin/assets/tags-Dmft-Y4O.js +0 -1
  411. package/core/built/admin/assets/toggle-group-R2QMFvXW.js +0 -1
  412. package/core/built/admin/assets/use-filter-params-WrdhAISv.js +0 -1
  413. package/core/built/admin/assets/web-BqW0b0_C.js +0 -1
  414. package/core/built/admin/assets/web-Cy5GKMWM.js +0 -2
  415. package/core/server/services/email-analytics/jobs/update-member-email-analytics/index.js +0 -14
  416. package/core/server/services/url/local-file-cache.js +0 -75
  417. package/core/server/services/url/queue.js +0 -260
  418. package/core/server/services/url/resource.js +0 -102
  419. package/core/server/services/url/resources.js +0 -480
  420. package/core/server/services/url/url-generator.js +0 -276
  421. package/core/server/services/url/url-service-facade.js +0 -366
  422. package/core/server/services/url/url-service-facade.ts +0 -519
  423. package/core/server/services/url/url-service.js +0 -387
  424. package/core/server/services/url/urls.js +0 -140
  425. package/core/shared/events/url-resource-updated-event.js +0 -33
  426. /package/core/built/admin/assets/{chunk.211.6f7328d63c41536451b3.js.LICENSE.txt → chunk.571.40f093ed6d883993714e.js.LICENSE.txt} +0 -0
  427. /package/core/built/admin/assets/{dist-CqO2lWS0.js → dist-CqO2lWS02.js} +0 -0
  428. /package/core/server/api/endpoints/{gift-reminders.js → gifts.js} +0 -0
@@ -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,
@@ -409,18 +413,16 @@ class MemberWelcomeEmailService {
409
413
  getSenderDetails(email.designSettings)
410
414
  );
411
415
 
412
- const headers = unsubscribe?.oneClickUrl ? {
413
- 'List-Unsubscribe': `<${unsubscribe.oneClickUrl}>`,
414
- 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
415
- } : undefined;
416
-
417
416
  /** @type {string[]} */ let tags;
417
+ let sendEmail;
418
418
  switch (emailType) {
419
419
  case 'welcome':
420
420
  tags = [MEMBER_WELCOME_EMAIL_TAG];
421
+ sendEmail = this.#sendTransactionalEmail.bind(this);
421
422
  break;
422
423
  case 'automation':
423
424
  tags = [AUTOMATION_EMAIL_TAG];
425
+ sendEmail = this.#sendBulkEmail.bind(this);
424
426
  break;
425
427
  default: {
426
428
  /** @type {never} */ const _exhaustive = emailType;
@@ -430,19 +432,78 @@ class MemberWelcomeEmailService {
430
432
  }
431
433
  }
432
434
 
433
- return await this.#mailer.send({
435
+ return await sendEmail({
434
436
  to: member.email,
435
437
  subject,
436
438
  html,
437
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,
438
471
  forceTextContent: true,
439
472
  tags,
440
473
  ...(headers ? {headers} : {}),
441
474
  ...(typeof trackOpens === 'boolean' ? {trackOpens} : {}),
442
- ...senderOptions
475
+ from,
476
+ replyTo
443
477
  });
444
478
  }
445
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
+
446
507
  async send({member, memberStatus}) {
447
508
  const email = this.#memberWelcomeEmails[memberStatus];
448
509
 
@@ -651,12 +712,11 @@ class MemberWelcomeEmailService {
651
712
  getSenderDetails(designSettingsJson)
652
713
  );
653
714
 
654
- await this.#mailer.send({
715
+ await this.#sendTransactionalEmail({
655
716
  to: email,
656
717
  subject: `[Test] ${renderedSubject}`,
657
718
  html,
658
719
  text,
659
- forceTextContent: true,
660
720
  ...senderOptions
661
721
  });
662
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),
@@ -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);
@@ -99,8 +99,7 @@ type CustomFieldPlan = unknown;
99
99
  // The custom fields collaborator as the import needs it. activeFields is the field set a
100
100
  // custom_fields.* column is read against, empty when the feature is off; planWrite
101
101
  // validates a row's values (throwing so the row fails whole) and applyWrite persists
102
- // them, merging a composite's sub-fields into whatever is already stored, both on the
103
- // row's transaction.
102
+ // them, touching only the parts the row named, both on the row's transaction.
104
103
  export interface CustomFieldsImport {
105
104
  activeFields(): Promise<CsvField[]>;
106
105
  planWrite(values: Record<string, unknown>): Promise<CustomFieldPlan[]>;
@@ -333,7 +332,7 @@ class MembersCSVImporter {
333
332
  // before any member write -- so there is no reason to hold a transaction
334
333
  // across it, and doing so would deadlock the single-connection SQLite pool.
335
334
  const customFieldPlan = activeCustomFields.length > 0
336
- ? await this._customFields.planWrite(fieldValuesFromCsvRow(activeCustomFields, row, stripFormulaGuard))
335
+ ? await namingTheColumn(() => this._customFields.planWrite(fieldValuesFromCsvRow(activeCustomFields, row, stripFormulaGuard)))
337
336
  : [];
338
337
 
339
338
  trx = await this._knex.transaction(undefined, {doNotRejectOnRollback: false});
@@ -433,15 +432,16 @@ class MembersCSVImporter {
433
432
  imported += 1;
434
433
  } catch (error) {
435
434
  const errorList: unknown[] = Array.isArray(error) ? error : [error];
436
- const errorMessage = errorList
435
+ const reasons = errorList
437
436
  .map(e => (typeof e === 'object' && e !== null && 'message' in e ? e.message : undefined))
438
- .join(', ');
437
+ .filter((message): message is string => typeof message === 'string');
438
+ const errorMessage = reasons.join('\n');
439
439
  // trx is unset if the row failed before the transaction opened (a bad
440
440
  // custom field value or gift combination).
441
441
  if (trx) {
442
442
  await trx.rollback();
443
443
  }
444
- importErrors.push({...row, error: errorMessage});
444
+ importErrors.push({...row, error: errorMessage, errors: reasons});
445
445
  }
446
446
  }
447
447
 
@@ -458,6 +458,26 @@ class MembersCSVImporter {
458
458
  }
459
459
  }
460
460
 
461
+ // A row error is read next to a spreadsheet, and a value rejection states only what the
462
+ // value should be — the same sentence Admin shows under a single input, where the field is
463
+ // already on screen. `property` is the dotted path a default export writes as its column
464
+ // header, so prefixing with it names the column a publisher has to go and fix.
465
+ //
466
+ // A rejection about the whole request carries the bare namespace and names no field after
467
+ // it, so it is left alone rather than made to point at a column that does not exist.
468
+ async function namingTheColumn<T>(plan: () => Promise<T>): Promise<T> {
469
+ try {
470
+ return await plan();
471
+ } catch (error) {
472
+ const {property, message} = (error ?? {}) as {property?: unknown; message?: unknown};
473
+ const namesAField = typeof property === 'string' && property.includes('.');
474
+ if (!namesAField || typeof message !== 'string') {
475
+ throw error;
476
+ }
477
+ throw new errors.DataImportError({message: `${property}: ${message}`});
478
+ }
479
+ }
480
+
461
481
  function wrapGiftError(error: unknown) {
462
482
  const err = error as {message?: unknown};
463
483
  const message = (err && typeof err.message === 'string' && err.message) || tpl(messages.giftReassignFailed);
@@ -42,7 +42,11 @@ export type MemberImportRow = z.infer<typeof memberImportRowSchema>;
42
42
 
43
43
  // A row that failed to import, carrying the raw message that stopped it. The message is
44
44
  // left raw here; turning it into human copy is a presentation concern.
45
- export type ImportErrorRow = MemberImportRow & {error: string};
45
+ // A row can fail for more than one reason. `errors` is the list; `error` is that list
46
+ // written out for the error report's single cell. Two fields because a reason may quote a
47
+ // cell the publisher wrote, and a CSV cell legally holds commas and newlines, so no
48
+ // separator survives being split back apart.
49
+ export type ImportErrorRow = MemberImportRow & {error: string; errors: string[]};
46
50
 
47
51
  // The persisted import label as plain data -- taken off the Bookshelf model before it
48
52
  // leaves the kernel, so the result and the email carry only what they show.
@@ -52,11 +52,7 @@ function makeImporter(deps) {
52
52
  const customFields = {
53
53
  activeFields: async () => (labs.isSet('membersCustomFields') ? deps.customFields.definitions.browse() : []),
54
54
  planWrite: values => deps.customFields.values.planWrite(values),
55
- // mergeComposites: a sub-field is a field, so a row writes the sub-fields it fills
56
- // and leaves the rest alone, the same rule a blank `name` column follows. Without
57
- // it a file naming one address column would replace the whole address, clearing
58
- // five sub-fields it never mentioned.
59
- applyWrite: (memberId, plan, executor) => deps.customFields.values.applyWrite(memberId, plan, executor, { mergeComposites: true })
55
+ applyWrite: (memberId, plan, executor) => deps.customFields.values.applyWrite(memberId, plan, { executor })
60
56
  };
61
57
  return new importer_1.default({
62
58
  knex: deps.knex,
@@ -33,7 +33,7 @@ interface ImporterServices {
33
33
  definitions: {browse(): Promise<CsvField[]>};
34
34
  values: {
35
35
  planWrite(values: Record<string, unknown>): Promise<unknown[]>;
36
- applyWrite(memberId: string, plan: unknown[], executor: Knex, options?: {mergeComposites?: boolean}): Promise<void>;
36
+ applyWrite(memberId: string, plan: unknown[], options?: {executor?: Knex}): Promise<void>;
37
37
  };
38
38
  };
39
39
  }
@@ -87,11 +87,7 @@ export function makeImporter(deps: ImporterServices) {
87
87
  const customFields: CustomFieldsImport = {
88
88
  activeFields: async () => (labs.isSet('membersCustomFields') ? deps.customFields.definitions.browse() : []),
89
89
  planWrite: values => deps.customFields.values.planWrite(values),
90
- // mergeComposites: a sub-field is a field, so a row writes the sub-fields it fills
91
- // and leaves the rest alone, the same rule a blank `name` column follows. Without
92
- // it a file naming one address column would replace the whole address, clearing
93
- // five sub-fields it never mentioned.
94
- applyWrite: (memberId, plan, executor) => deps.customFields.values.applyWrite(memberId, plan, executor, {mergeComposites: true})
90
+ applyWrite: (memberId, plan, executor) => deps.customFields.values.applyWrite(memberId, plan, {executor})
95
91
  };
96
92
 
97
93
  return new MembersCSVImporter({
@@ -1256,6 +1256,11 @@ module.exports = class MemberRepository {
1256
1256
  };
1257
1257
  let eventData = {};
1258
1258
 
1259
+ // A cancellation (or reactivation) changes no `members` column, so the member
1260
+ // model event would be suppressed by `wasChanged()`. Remember the pre-update
1261
+ // subscription so the event can be marked and carry its prior state.
1262
+ let subscriptionBeforeCancelFlagChange = null;
1263
+
1259
1264
  const stripeCustomerSubscriptionModelShouldBeDeleted = stripeSubscriptionData.metadata && !!stripeSubscriptionData.metadata.ghost_migrated_to && stripeSubscriptionData.status === 'canceled';
1260
1265
  if (stripeCustomerSubscriptionModelShouldBeDeleted) {
1261
1266
  logging.warn(`Subscription ${subscriptionData.subscription_id} is marked for deletion, skipping linking.`);
@@ -1300,6 +1305,10 @@ module.exports = class MemberRepository {
1300
1305
  this.dispatchEvent(offerRedemptionEvent, options);
1301
1306
  }
1302
1307
 
1308
+ if (stripeCustomerSubscriptionModel.get('cancel_at_period_end') !== updatedStripeCustomerSubscriptionModel.get('cancel_at_period_end')) {
1309
+ subscriptionBeforeCancelFlagChange = stripeCustomerSubscriptionModel;
1310
+ }
1311
+
1303
1312
  if (stripeCustomerSubscriptionModel.get('mrr') !== updatedStripeCustomerSubscriptionModel.get('mrr') || stripeCustomerSubscriptionModel.get('plan_id') !== updatedStripeCustomerSubscriptionModel.get('plan_id') || stripeCustomerSubscriptionModel.get('status') !== updatedStripeCustomerSubscriptionModel.get('status') || stripeCustomerSubscriptionModel.get('cancel_at_period_end') !== updatedStripeCustomerSubscriptionModel.get('cancel_at_period_end')) {
1304
1313
  const originalMrrDelta = stripeCustomerSubscriptionModel.get('mrr');
1305
1314
  const updatedMrrDelta = updatedStripeCustomerSubscriptionModel.get('mrr');
@@ -1550,6 +1559,48 @@ module.exports = class MemberRepository {
1550
1559
  updatedMember = await this._Member.edit({status: status}, {...options, id: data.id});
1551
1560
  }
1552
1561
 
1562
+ // Cancelling (or reactivating) touches no `members` column, so mark the
1563
+ // subscription relation as the change and carry its prior state. This lets the
1564
+ // webhook payload express before/after — see `services/webhooks/serialize.js`.
1565
+ //
1566
+ // No explicit `emitChange` is needed, and adding one would emit twice: inside a
1567
+ // transaction `emitChange` queues onto the `committed` handler and defers its
1568
+ // `wasChanged()` check to commit time (see `models/base/plugins/events.js`), so
1569
+ // the event already queued by `_Member.edit` above is still pending and setting
1570
+ // `_changed` here is what lets it through. The e2e test asserting exactly one
1571
+ // `member.edited` per cancellation guards this.
1572
+ if (subscriptionBeforeCancelFlagChange && updatedMember) {
1573
+ // Price/tier relations are needed for the serialized subscription shape
1574
+ // to match the Admin API member resource (price, tier, plan)
1575
+ await updatedMember.load([
1576
+ 'stripeSubscriptions',
1577
+ 'stripeSubscriptions.stripePrice',
1578
+ 'stripeSubscriptions.stripePrice.stripeProduct',
1579
+ 'stripeSubscriptions.stripePrice.stripeProduct.product'
1580
+ ], options);
1581
+ await subscriptionBeforeCancelFlagChange.load([
1582
+ 'stripePrice',
1583
+ 'stripePrice.stripeProduct',
1584
+ 'stripePrice.stripeProduct.product'
1585
+ ], options);
1586
+ updatedMember._changed = {
1587
+ ...updatedMember._changed,
1588
+ stripeSubscriptions: {
1589
+ cancel_at_period_end: subscriptionBeforeCancelFlagChange.get('cancel_at_period_end')
1590
+ }
1591
+ };
1592
+ // `previous` must be the full collection — a member can have multiple
1593
+ // subscriptions — with only the changed one swapped for its prior state
1594
+ updatedMember._previousRelations = {
1595
+ ...updatedMember._previousRelations,
1596
+ stripeSubscriptions: {
1597
+ models: updatedMember.related('stripeSubscriptions').models.map((subscription) => {
1598
+ return subscription.id === subscriptionBeforeCancelFlagChange.id ? subscriptionBeforeCancelFlagChange : subscription;
1599
+ })
1600
+ }
1601
+ };
1602
+ }
1603
+
1553
1604
  const newMemberProductIds = memberProducts.map(product => product.id);
1554
1605
  const oldMemberProductIds = oldMemberProducts.map(product => product.id);
1555
1606
 
@@ -3,19 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.activeFields = activeFields;
4
4
  const schema_1 = require("./schema");
5
5
  const FIELDS_TABLE = 'members_custom_fields';
6
- // Shared query builders over the field definitions, used by both the definitions
7
- // service and the values service.
8
- //
9
- // The `status = 'active'` filter is the one invariant worth centralising: archived
10
- // fields must stay out of every read and every write, and that's enforced by a
11
- // filter, not a database constraint — nothing stops a value row referencing an
12
- // archived field, so a query that forgets the filter is a silent bug. Keeping the
13
- // filter in one builder means there's one place to get it right.
14
- /**
15
- * The active field definitions. Takes the executor — a knex instance or a
16
- * transaction — so the same query runs standalone or inside a write's
17
- * transaction; callers chain their own `select`/`orderBy`/`whereIn` onto it.
18
- */
6
+ // Archived fields must stay out of every read and write, and nothing in the database
7
+ // enforces that no constraint stops a value row referencing an archived field. A query
8
+ // that forgets the filter is a silent bug, so the filter lives in one place.
9
+ /** Takes the executor so the same query runs standalone or inside a write's transaction. */
19
10
  function activeFields(db) {
20
11
  return db(FIELDS_TABLE).where('status', schema_1.FIELD_STATUS.active);
21
12
  }
@@ -3,20 +3,11 @@ import {FIELD_STATUS} from './schema';
3
3
 
4
4
  const FIELDS_TABLE = 'members_custom_fields';
5
5
 
6
- // Shared query builders over the field definitions, used by both the definitions
7
- // service and the values service.
8
- //
9
- // The `status = 'active'` filter is the one invariant worth centralising: archived
10
- // fields must stay out of every read and every write, and that's enforced by a
11
- // filter, not a database constraint — nothing stops a value row referencing an
12
- // archived field, so a query that forgets the filter is a silent bug. Keeping the
13
- // filter in one builder means there's one place to get it right.
6
+ // Archived fields must stay out of every read and write, and nothing in the database
7
+ // enforces that no constraint stops a value row referencing an archived field. A query
8
+ // that forgets the filter is a silent bug, so the filter lives in one place.
14
9
 
15
- /**
16
- * The active field definitions. Takes the executor — a knex instance or a
17
- * transaction — so the same query runs standalone or inside a write's
18
- * transaction; callers chain their own `select`/`orderBy`/`whereIn` onto it.
19
- */
10
+ /** Takes the executor so the same query runs standalone or inside a write's transaction. */
20
11
  export function activeFields(db: Knex) {
21
12
  return db(FIELDS_TABLE).where('status', FIELD_STATUS.active);
22
13
  }
@@ -1,21 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DbCustomFieldValueWithField = exports.StoredCompositeValue = exports.DbCustomFieldValue = exports.DbCustomField = exports.FieldStatusSchema = exports.FIELD_STATUS = void 0;
3
+ exports.DbCustomFieldLeaf = exports.DbCustomFieldValue = exports.DbCustomField = exports.FieldStatusSchema = exports.FIELD_STATUS = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const custom_field_types_1 = require("@tryghost/custom-field-types");
6
6
  const db_date_1 = require("../../lib/db-date");
7
- // A field's lifecycle state. `archived` is a soft state: the field drops out of
8
- // the values path but stays visible in the definition list (with its status) so
9
- // admins can find, rename, restore, or permanently delete it. The values mirror
10
- // schema.js's `isIn` constraint on the column — which is static config and can't
11
- // import this, so that one stays literal with a pointer back here.
7
+ // `archived` is soft: the field drops out of the values path but stays in the definition
8
+ // list so it can be renamed, restored or deleted. Mirrors schema.js's `isIn` on the
9
+ // column, which is static config and cannot import this.
12
10
  exports.FIELD_STATUS = { active: 'active', archived: 'archived' };
13
11
  exports.FieldStatusSchema = zod_1.z.enum([exports.FIELD_STATUS.active, exports.FIELD_STATUS.archived]);
14
- // The members_custom_fields row: the single source for the read projection and the
15
- // knex table type below. `type` is validated as the field-type enum here (the DB
16
- // only stores registered types), so the row already carries the narrow type and
17
- // the definition codec needs no cast. `status` travels with the row: it's part of
18
- // the read projection so the definition list can group active vs archived.
12
+ // The single source for the read projection and the knex table type below. `type` parses
13
+ // as the field-type enum, so the row carries the narrow type and no codec needs a cast.
19
14
  exports.DbCustomField = zod_1.z.object({
20
15
  id: zod_1.z.string(),
21
16
  key: zod_1.z.string(),
@@ -25,32 +20,28 @@ exports.DbCustomField = zod_1.z.object({
25
20
  created_at: db_date_1.DbDate,
26
21
  updated_at: db_date_1.DbDate.nullable()
27
22
  });
28
- // A member's stored value for one field. `value_text`/`value_json` are the raw
29
- // columns — which one carries the value, and how it decodes, is the storage
30
- // codec's business (see storage.ts), so they're plain nullable strings here.
23
+ // One part of a member's value. What a `path` means is storage.ts's business, so the row
24
+ // carries it as a plain string.
31
25
  exports.DbCustomFieldValue = zod_1.z.object({
32
26
  id: zod_1.z.string(),
33
27
  custom_field_id: zod_1.z.string(),
34
28
  member_id: zod_1.z.string(),
29
+ path: zod_1.z.string(),
30
+ // Nullable like the column, though nothing here writes a null: a part with no value
31
+ // has no row.
35
32
  value_text: zod_1.z.string().nullable(),
36
- value_json: zod_1.z.string().nullable(),
37
33
  created_at: db_date_1.DbDate,
38
34
  updated_at: db_date_1.DbDate.nullable()
39
35
  });
40
- // A composite value once decoded: sub-field keys to values, and no narrower. Which
41
- // sub-fields a composite has is its field type's business, not the row's.
36
+ // The field's key travels with the row so a value assembles without a second lookup.
42
37
  //
43
- // It doubles as the guard on a stored blob. A JSON column can just as easily hold an
44
- // array, a null or a bare number, and a record rejects all three so parsing against
45
- // this is what narrows the type, with no assertion needed.
46
- exports.StoredCompositeValue = zod_1.z.record(zod_1.z.string(), zod_1.z.unknown());
47
- // The value join a read needs: the field's identity and type travel with the
48
- // stored columns, so a row can be decoded without a second lookup. `type` is
49
- // parsed as the field-type enum, which narrows it with no cast.
50
- exports.DbCustomFieldValueWithField = zod_1.z.object({
38
+ // `type` takes no part in the assembly and is here as a gate: a value whose type has left
39
+ // the catalog is one the definitions list no longer returns either, so failing to parse
40
+ // is what drops it.
41
+ exports.DbCustomFieldLeaf = zod_1.z.object({
51
42
  member_id: zod_1.z.string(),
52
43
  key: zod_1.z.string(),
53
44
  type: custom_field_types_1.FieldTypeSchema,
54
- value_text: zod_1.z.string().nullable(),
55
- value_json: zod_1.z.string().nullable()
45
+ path: zod_1.z.string(),
46
+ value_text: zod_1.z.string()
56
47
  });