ghost 6.60.0 → 6.62.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 (613) hide show
  1. package/components/ghost-storage-base-3.0.0.tgz +0 -0
  2. package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
  3. package/components/tryghost-adapter-base-jobs-0.0.0.tgz +0 -0
  4. package/components/tryghost-adapter-base-redirects-0.0.0.tgz +0 -0
  5. package/components/tryghost-adapter-base-route-settings-0.0.0.tgz +0 -0
  6. package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
  7. package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
  8. package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
  9. package/components/tryghost-checkout-0.0.0.tgz +0 -0
  10. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  11. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  12. package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
  13. package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
  14. package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
  15. package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
  16. package/components/tryghost-kg-default-nodes-2.2.1.tgz +0 -0
  17. package/components/tryghost-kg-default-transforms-1.3.4.tgz +0 -0
  18. package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
  19. package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
  20. package/components/tryghost-kg-markdown-html-renderer-7.2.4.tgz +0 -0
  21. package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
  22. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  23. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  24. package/content/settings/README.md +1 -1
  25. package/content/themes/casper/.gitignore +28 -0
  26. package/content/themes/casper/assets/built/casper.js +1 -1
  27. package/content/themes/casper/assets/built/screen.css +1 -1
  28. package/content/themes/casper/assets/css/screen.css +2 -1
  29. package/content/themes/casper/assets/js/lightbox.js +63 -46
  30. package/content/themes/casper/package.json +6 -6
  31. package/content/themes/casper/page.hbs +1 -0
  32. package/content/themes/casper/post.hbs +1 -0
  33. package/content/themes/source/.gitignore +28 -0
  34. package/content/themes/source/assets/built/screen.css +1 -1
  35. package/content/themes/source/assets/built/source.js +1 -1
  36. package/content/themes/source/assets/css/screen.css +2 -1
  37. package/content/themes/source/assets/js/lightbox.js +62 -45
  38. package/content/themes/source/assets/js/main.js +2 -2
  39. package/content/themes/source/package.json +4 -4
  40. package/content/themes/source/partials/feature-image.hbs +1 -0
  41. package/core/boot.js +62 -21
  42. package/core/built/admin/assets/Combination-B0l4MKVh.js +41 -0
  43. package/core/built/admin/assets/InterVariable-DiVDrmQJ.woff2 +0 -0
  44. package/core/built/admin/assets/InterVariable-Italic-FCBEiFp6.woff2 +0 -0
  45. package/core/built/admin/assets/{PolarAngleAxis-C4qtHy1B.js → PolarAngleAxis-pkMyqz8g.js} +1 -1
  46. package/core/built/admin/assets/{RadialBarChart-Bh09Stsj.js → RadialBarChart-JAKoS4KT.js} +1 -1
  47. package/core/built/admin/assets/{about-BgpmhCuM.js → about-Dyae_UT6.js} +1 -1
  48. package/core/built/admin/assets/account-migration-CESa0mnX.js +1 -0
  49. package/core/built/admin/assets/{action-list-lGzZLHc8.js → action-list-C4v0-du8.js} +1 -1
  50. package/core/built/admin/assets/activitypub/{account-migration-DMHuPnJn.js → account-migration-By69CgCp.js} +16 -16
  51. package/core/built/admin/assets/activitypub/activitypub.js +9 -10
  52. package/core/built/admin/assets/activitypub/{avatar-DhTo5D1B.js → avatar-DLPL3Abs.js} +59 -68
  53. package/core/built/admin/assets/activitypub/{avatar-flipboard-DnVgbEfa.js → avatar-flipboard-C9iDY-QV.js} +2 -2
  54. package/core/built/admin/assets/activitypub/{bluesky-sharing-BT4zAlW7.js → bluesky-sharing-BJcPMrDd.js} +37 -37
  55. package/core/built/admin/assets/activitypub/{content-formatters-BeDZ-DIa.js → content-formatters-3rTHg3mp.js} +18 -18
  56. package/core/built/admin/assets/activitypub/{copy-8sqxUuNd.js → copy-DIAOQoCZ.js} +2 -2
  57. package/core/built/admin/assets/activitypub/{domain-xQDDu1CS.js → domain-DUR4xSkV.js} +121 -121
  58. package/core/built/admin/assets/activitypub/{edit-profile-BXdF_r2G.js → edit-profile-BqtfzxCp.js} +40 -40
  59. package/core/built/admin/assets/activitypub/{explore-CMt7gKlL.js → explore-BxuJd-J4.js} +27 -27
  60. package/core/built/admin/assets/activitypub/{feed-Bwqdddp_.js → feed-BM1TcksA.js} +51 -51
  61. package/core/built/admin/assets/activitypub/{field-4co_8FVg.js → field-Dn7HHx2-.js} +3 -3
  62. package/core/built/admin/assets/activitypub/{hash-DwakMs4X.js → hash-Bm3WxYN4.js} +2 -2
  63. package/core/built/admin/assets/activitypub/inbox-CCjkuow2.js +1284 -0
  64. package/core/built/admin/assets/activitypub/{inbox-ckD4wo_r.js → inbox-d8oJRTf4.js} +2 -2
  65. package/core/built/admin/assets/activitypub/label-1AA_s9Fa.js +16 -0
  66. package/core/built/admin/assets/activitypub/layout-2-0bw6HN.js +1 -0
  67. package/core/built/admin/assets/activitypub/{moderation-KCCO7L2J.js → moderation-CnmxEB8w.js} +91 -91
  68. package/core/built/admin/assets/activitypub/{note-BX_Bz5A0.js → note-ZKc_Bnba.js} +24 -24
  69. package/core/built/admin/assets/activitypub/{notifications-BQOYGvRv.js → notifications-CB2tJZau.js} +51 -51
  70. package/core/built/admin/assets/activitypub/{onboarding-CNelD_uF.js → onboarding-BsJ9E5qf.js} +2 -2
  71. package/core/built/admin/assets/activitypub/{onboarding-mat5Hrl0.js → onboarding-CSUJJIlh.js} +2 -2
  72. package/core/built/admin/assets/activitypub/{preferences-6h43G9_q.js → preferences-0-JZnUkI.js} +1519 -1519
  73. package/core/built/admin/assets/activitypub/{profile-BMN9VVZ2.js → profile-C35cFIhM.js} +256 -256
  74. package/core/built/admin/assets/activitypub/{reply-ZNo04xsO.js → reply-DYK5vVqb.js} +2 -2
  75. package/core/built/admin/assets/activitypub/{routes-3B9mUQ7i.js → routes-BD4vscyc.js} +796 -790
  76. package/core/built/admin/assets/activitypub/{separator-D9taVCCa.js → separator-BbQqTm-p.js} +3 -3
  77. package/core/built/admin/assets/activitypub/settings-BsMROdqh.js +315 -0
  78. package/core/built/admin/assets/activitypub/{site-DHoMupSB.js → site-O0RL9YCN.js} +2 -2
  79. package/core/built/admin/assets/activitypub/{step-1-BsGGnBuT.js → step-1-BMSx7c_X.js} +6 -6
  80. package/core/built/admin/assets/activitypub/{step-2-teosIS0k.js → step-2-DtPQzlTH.js} +6 -6
  81. package/core/built/admin/assets/activitypub/{step-3-BjD5TokC.js → step-3-CLlOZTyt.js} +11 -11
  82. package/core/built/admin/assets/activitypub/{tabs-Cvwl1wXU.js → tabs-Dxk-b9M-.js} +306 -306
  83. package/core/built/admin/assets/activitypub/{topic-filter-C3LZJc2X.js → topic-filter-5yZH6tId.js} +3 -3
  84. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-Dp9f0gov.js → use-activity-pub-queries-CV50qe03.js} +3 -3
  85. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-C4RI0sa9.js → use-navigate-with-base-path-Bmk5h4lW.js} +220 -220
  86. package/core/built/admin/assets/activitypub/{use-reply-chain-data-C2oNDMUH.js → use-reply-chain-data-Cbvzgjzd.js} +30 -30
  87. package/core/built/admin/assets/activitypub/{users-CoX6FT_K.js → users-BjcoP_HC.js} +7 -5
  88. package/core/built/admin/assets/activitypub/{x-Dxm6pV9X.js → x-DbQZOLiI.js} +2 -2
  89. package/core/built/admin/assets/add-integration-modal-BlF6rs_f.js +1 -0
  90. package/core/built/admin/assets/add-newsletter-modal-CglVQqah.js +1 -0
  91. package/core/built/admin/assets/add-recommendation-modal-wz7BsuaG.js +1 -0
  92. package/core/built/admin/assets/analytics-C4HE1gBg.js +1 -0
  93. package/core/built/admin/assets/{announcement-bar-modal-BTHiy1ZO.js → announcement-bar-modal-Ci4zz8jO.js} +1 -1
  94. package/core/built/admin/assets/{api-keys-Boy5pLbD.js → api-keys-Ck-Yteiv.js} +1 -1
  95. package/core/built/admin/assets/audience-CtbA54Sn.js +1 -0
  96. package/core/built/admin/assets/{automated-emails-te1D5kq-.js → automated-emails-D5VVMND9.js} +1 -1
  97. package/core/built/admin/assets/automations-CzKt5fb8.js +1 -0
  98. package/core/built/admin/assets/bluesky-sharing-BEtJ_gL_.js +1 -0
  99. package/core/built/admin/assets/{box-By_EgGtm.js → box-CdfQAP_F.js} +1 -1
  100. package/core/built/admin/assets/{brand-icon-09YqFZjV.js → brand-icon-CrIGOA3Z.js} +1 -1
  101. package/core/built/admin/assets/{breadcrumb-CcFr_ltt.js → breadcrumb-zUrZrBtW.js} +1 -1
  102. package/core/built/admin/assets/{button-BshWPHvn.js → button-eNMKQWXr.js} +1 -1
  103. package/core/built/admin/assets/{card-CiiE7QfM.js → card--VNbfgo3.js} +1 -1
  104. package/core/built/admin/assets/{chart-BnXyfH11.js → chart-BrhA41Zi.js} +1 -1
  105. package/core/built/admin/assets/chart-helpers-CfBKt0vT.js +1 -0
  106. package/core/built/admin/assets/{checkbox-Dwwr_3Py.js → checkbox-CgOn_tKW.js} +1 -1
  107. package/core/built/admin/assets/{chunk.524.91ecb33e6afc3bd8949b.js → chunk.524.a697dee88db67fdf6021.js} +4 -4
  108. package/core/built/admin/assets/{chunk.582.512549909741883f960c.js → chunk.582.5ed5d124cfca9c259b9c.js} +6 -6
  109. package/core/built/admin/assets/{chunk.16.f76a6314082d813d67e0.js → chunk.598.7e056fabb47ce7dbd73f.js} +14 -14
  110. package/core/built/admin/assets/{code-editor-view-BwIdPy_k.js → code-editor-view-BYNoAoNV.js} +1 -1
  111. package/core/built/admin/assets/{code-editor-view-CtkF82Fv.js → code-editor-view-K2wI-I5f.js} +1 -1
  112. package/core/built/admin/assets/color-picker-Ddw80xX6.js +3 -0
  113. package/core/built/admin/assets/color-picker-field-D9nUhvA7.js +1 -0
  114. package/core/built/admin/assets/combobox-w9j8PPQT.js +1 -0
  115. package/core/built/admin/assets/command-BfpHORBv.js +1 -0
  116. package/core/built/admin/assets/comments-BLmCmyCs.js +1 -0
  117. package/core/built/admin/assets/confirmation-context-CBM8G0Vj.js +1 -0
  118. package/core/built/admin/assets/{container-LiZf3XdY.js → container-BuQG1wuP.js} +1 -1
  119. package/core/built/admin/assets/{content-api-modal-BtpNI92-.js → content-api-modal-TXRz3U7S.js} +1 -1
  120. package/core/built/admin/assets/context-menu-_QMdd3kp.js +1 -0
  121. package/core/built/admin/assets/{copy-field-CglgRZyG.js → copy-field-6N01S07E.js} +1 -1
  122. package/core/built/admin/assets/create-ghost-browse-value-source-qIBymvvf.js +1 -0
  123. package/core/built/admin/assets/custom-field-type-option-BddFlJFa.js +1 -0
  124. package/core/built/admin/assets/custom-integration-modal-DmNquxUV.js +1 -0
  125. package/core/built/admin/assets/{data-list-CupF7bjM.js → data-list-CA2jYGPq.js} +1 -1
  126. package/core/built/admin/assets/{date-range-select-JPwM0yUj.js → date-range-select-uvz_DtPh.js} +1 -1
  127. package/core/built/admin/assets/design-and-theme-modal-BSmo12Op.js +2 -0
  128. package/core/built/admin/assets/{detail-page-D8lx2SdP.js → detail-page-Dnv-CdR1.js} +1 -1
  129. package/core/built/admin/assets/dirty-confirm-dialog-BZ1iytEd.js +1 -0
  130. package/core/built/admin/assets/{disabled-sources-indicator-BTJ2ClQu.js → disabled-sources-indicator-DMRrxsiJ.js} +1 -1
  131. package/core/built/admin/assets/{dist-DzcfoDJ3.js → dist-8YJUlf3B.js} +1 -1
  132. package/core/built/admin/assets/{dist-Cb5RSHSg.js → dist-B3keLvjj.js} +1 -1
  133. package/core/built/admin/assets/dist-Dcsslyo72.js +1 -0
  134. package/core/built/admin/assets/{dist-BT_Z-eCn.js → dist-Dgt2cr6a.js} +1 -1
  135. package/core/built/admin/assets/domain-BNYrt0aD.js +1 -0
  136. package/core/built/admin/assets/dropzone-D7nFdAe5.js +5 -0
  137. package/core/built/admin/assets/edit-profile-C5Asm4LA.js +1 -0
  138. package/core/built/admin/assets/editor-REokrzci.js +8 -0
  139. package/core/built/admin/assets/{embed-signup-form-modal-BFTPf04i.js → embed-signup-form-modal-Ds6mPEi5.js} +1 -1
  140. package/core/built/admin/assets/{empty-indicator-WB1WgT9h.js → empty-indicator-D9QdpuNX.js} +1 -1
  141. package/core/built/admin/assets/en-B1jX3WMs.js +1 -0
  142. package/core/built/admin/assets/explore-UY10Egsp.js +1 -0
  143. package/core/built/admin/assets/feature-image-placeholder-D1iF_8bc.js +1 -0
  144. package/core/built/admin/assets/feed-81C8mJWR.js +1 -0
  145. package/core/built/admin/assets/{field-LmNk7wZh.js → field-Dihp5p18.js} +1 -1
  146. package/core/built/admin/assets/filter-bar-Da1qxybu.js +1 -0
  147. package/core/built/admin/assets/filter-sources-BO9xUPj7.js +1 -0
  148. package/core/built/admin/assets/filters-CRUWecfY.js +1 -0
  149. package/core/built/admin/assets/filters-DirqNXjH.js +1 -0
  150. package/core/built/admin/assets/first-promoter-modal-CT6MxYZb.js +1 -0
  151. package/core/built/admin/assets/{ghost-c8807202a07c20030acb719ee0114815.css → ghost-8eabfc1ece81b03cf7b90780d31487e8.css} +1 -1
  152. package/core/built/admin/assets/{ghost-403cb39badad3df411fc0ba27d220047.js → ghost-c2848508292937415f20f5a830eea59e.js} +31 -28
  153. package/core/built/admin/assets/{ghost-dark-fbf1e384aaef3ae315e540f0eb7373a7.css → ghost-dark-a7bebe2c561f55369dcf29f4c820f7ff.css} +1 -1
  154. package/core/built/admin/assets/gift-link-modal-CQVAye17.js +1 -0
  155. package/core/built/admin/assets/{grid-CUciBfjf.js → grid-Dj29PY05.js} +1 -1
  156. package/core/built/admin/assets/growth-BWIu-6xj.js +3 -0
  157. package/core/built/admin/assets/growth-DU29oxeA.js +1 -0
  158. package/core/built/admin/assets/{heading-CRZZ2FUe.js → heading-Dh5SFVjL.js} +1 -1
  159. package/core/built/admin/assets/history-modal-CFd1Hvx6.js +1 -0
  160. package/core/built/admin/assets/html-field-CJBCLTvv.js +1 -0
  161. package/core/built/admin/assets/{images-C0XvUYMb.js → images-scfkWZiK.js} +1 -1
  162. package/core/built/admin/assets/{inbox-DZXDUt4s.js → inbox-Mcp4DXZN.js} +14 -14
  163. package/core/built/admin/assets/index-6MkPdCtr.js +4 -0
  164. package/core/built/admin/assets/index-Bn7QhcZu.css +2 -0
  165. package/core/built/admin/assets/index.es-CNnuNH0d.js +1 -0
  166. package/core/built/admin/assets/{inline-Cw-gigDW.js → inline-BWSs_VzR.js} +1 -1
  167. package/core/built/admin/assets/{input-group-BPAxETpQ.js → input-group-CMhe2DvY.js} +1 -1
  168. package/core/built/admin/assets/{input-surface-BdOe4KzO.js → input-surface-BsrIZeSF.js} +1 -1
  169. package/core/built/admin/assets/{integration-header-B4DyXV06.js → integration-header-Bm6IDb1A.js} +1 -1
  170. package/core/built/admin/assets/invite-user-modal-DfheMXs0.js +1 -0
  171. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +14 -14
  172. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +3 -3
  173. package/core/built/admin/assets/koenig-lexical/style.css +1 -1
  174. package/core/built/admin/assets/{koenig-lexical-60O_8aiO.js → koenig-lexical-DBSqAuiI.js} +3 -3
  175. package/core/built/admin/assets/koenig-loader-aFQNlJlD.js +2 -0
  176. package/core/built/admin/assets/kpi-card-DBRMfGOW.js +1 -0
  177. package/core/built/admin/assets/{kpi-card-fUHZlzBX.js → kpi-card-DFafD1qq.js} +1 -1
  178. package/core/built/admin/assets/kpi-helpers-Cv8i8hns.js +1 -0
  179. package/core/built/admin/assets/kpi-tabs-BJItvLYm.js +1 -0
  180. package/core/built/admin/assets/{label-VIG969IC.js → label-BnSCvffu.js} +1 -1
  181. package/core/built/admin/assets/label-picker-KVoo91sh.js +1 -0
  182. package/core/built/admin/assets/layout-Bjv2Ll-5.js +1 -0
  183. package/core/built/admin/assets/{link-helpers-Dtq9fxQg.js → link-helpers-MVDdnFCY.js} +1 -1
  184. package/core/built/admin/assets/{list-page-CdsnQR9N.js → list-page-BucXJ0hi.js} +1 -1
  185. package/core/built/admin/assets/member-custom-fields-DDQV6QeG.js +1 -0
  186. package/core/built/admin/assets/member-detail-CdJF6Agw.js +1 -0
  187. package/core/built/admin/assets/members-DjQbq9Z2.js +4 -0
  188. package/core/built/admin/assets/{modal-page-BMTzFHVO.js → modal-page-xCivTMvI.js} +1 -1
  189. package/core/built/admin/assets/moderation-AgCIEso4.js +1 -0
  190. package/core/built/admin/assets/{multi-select-combobox-CGohnsxJ.js → multi-select-combobox-BcITkUVr.js} +1 -1
  191. package/core/built/admin/assets/navigation-modal-BkT7b1M2.js +1 -0
  192. package/core/built/admin/assets/newsletter-BMMAuvgq.js +1 -0
  193. package/core/built/admin/assets/newsletter-detail-modal-0SCJ7Zpm.js +1 -0
  194. package/core/built/admin/assets/{newsletter-emails-D5ql1rud.js → newsletter-emails-DKzwafuh.js} +1 -1
  195. package/core/built/admin/assets/newsletters-BRBb_uQC.js +1 -0
  196. package/core/built/admin/assets/note-DyGHtS9a.js +1 -0
  197. package/core/built/admin/assets/notifications-ZgWniKna.js +1 -0
  198. package/core/built/admin/assets/nql-BLpCn7qX.js +21 -0
  199. package/core/built/admin/assets/offers-container-modal-B-E8TIWr.js +1 -0
  200. package/core/built/admin/assets/{onboarding-B4YhlZ0i.js → onboarding-CzIsre-d.js} +1 -1
  201. package/core/built/admin/assets/onboarding-qg8Akovm.js +1 -0
  202. package/core/built/admin/assets/{onboarding-route-Cu_ROWPn.js → onboarding-route-CaavZulg.js} +1 -1
  203. package/core/built/admin/assets/overview-BW8GLeYi.js +1 -0
  204. package/core/built/admin/assets/overview-BkXZNzaZ.js +1 -0
  205. package/core/built/admin/assets/{page-header-Cfds8pCg.js → page-header-De0VzKBi.js} +1 -1
  206. package/core/built/admin/assets/pagemenu-B5KKCpbA.js +1 -0
  207. package/core/built/admin/assets/pages-jgaFm69d.js +1 -0
  208. package/core/built/admin/assets/pages-route-CIUAQxtY.js +1 -0
  209. package/core/built/admin/assets/papaparse.min-cQwnxGCG.js +10 -0
  210. package/core/built/admin/assets/{pintura-modal-TDTrJ89x.js → pintura-modal-BZ2J9w3n.js} +1 -1
  211. package/core/built/admin/assets/portal-frame-B6Hr9wHc.js +1 -0
  212. package/core/built/admin/assets/portal-modal-D4ceei3t.js +1 -0
  213. package/core/built/admin/assets/post-analytics-header-BYCsT0GP.js +1 -0
  214. package/core/built/admin/assets/post-analytics-ot7f6pih.js +1 -0
  215. package/core/built/admin/assets/{post-analytics-provider-Cu3jNeFh.js → post-analytics-provider-Bo1gmYaP.js} +1 -1
  216. package/core/built/admin/assets/{post-share-modal-P1_M6o-H.js → post-share-modal-C-PaYZ_e.js} +1 -1
  217. package/core/built/admin/assets/posts-DuGazgTb.js +1 -0
  218. package/core/built/admin/assets/posts-list-screen-CBGGgjjj.js +2 -0
  219. package/core/built/admin/assets/posts-route-D6xmS7Vf.js +1 -0
  220. package/core/built/admin/assets/preferences-C5YJLp74.js +5 -0
  221. package/core/built/admin/assets/{preview-chrome-BOVO6_PE.js → preview-chrome-BljUBlOk.js} +1 -1
  222. package/core/built/admin/assets/{preview-modal-MlTjP8UU.js → preview-modal-BfeFqi5y.js} +1 -1
  223. package/core/built/admin/assets/profile-B8mx2yAu.js +1 -0
  224. package/core/built/admin/assets/{radio-group-CjtmsCPl.js → radio-group-DczlLKOn.js} +1 -1
  225. package/core/built/admin/assets/{router-provider-CJC58zTy.js → router-provider-B0gZyiGJ.js} +1 -1
  226. package/core/built/admin/assets/{schemas-CRsC53ls.js → schemas-DppqmpeJ.js} +2 -2
  227. package/core/built/admin/assets/select-cb0t_TAJ.js +1 -0
  228. package/core/built/admin/assets/settings-DZqxR0Da.js +1 -0
  229. package/core/built/admin/assets/settings-DvfTNdHQ.js +4 -0
  230. package/core/built/admin/assets/{settings-breadcrumbs-sLDNedBW.js → settings-breadcrumbs-DLzbRRSt.js} +1 -1
  231. package/core/built/admin/assets/{shade-app-F7WxOyfk-nufqw1hd.js → shade-app-CcS3f8MZ-vwURazk8.js} +2 -2
  232. package/core/built/admin/assets/share-modal-D2-Bz1_I.js +1 -0
  233. package/core/built/admin/assets/{skeleton-D7hwjdry.js → skeleton-Ce9GgUpf.js} +1 -1
  234. package/core/built/admin/assets/slack-modal-DYDTUria.js +1 -0
  235. package/core/built/admin/assets/sort-button-VVeQjjbu.js +1 -0
  236. package/core/built/admin/assets/{source-icon-BPsD5ees.js → source-icon-Ck5_5Kgq.js} +1 -1
  237. package/core/built/admin/assets/{stack-CIfOAAVf.js → stack-B2eszIiq.js} +1 -1
  238. package/core/built/admin/assets/stats-Rjmk5owg.js +1 -0
  239. package/core/built/admin/assets/stats-view-BGGmkmgv.js +1 -0
  240. package/core/built/admin/assets/{step-1-CMta6YFO.js → step-1-CsaBjeN4.js} +1 -1
  241. package/core/built/admin/assets/{step-2-BG04nh5q.js → step-2-D2vnxOOM.js} +1 -1
  242. package/core/built/admin/assets/{step-3-ZnMaVIPj.js → step-3-wBoLdGEb.js} +2 -2
  243. package/core/built/admin/assets/{stripe-button-DDoUKN5a.js → stripe-button-CDgLz0S0.js} +1 -1
  244. package/core/built/admin/assets/stripe-connect-modal-CSjMJetP.js +1 -0
  245. package/core/built/admin/assets/{switch-C9djUIMO.js → switch-CI4gLWqb.js} +1 -1
  246. package/core/built/admin/assets/{table-CrqQtkR2.js → table-Do6iQ83Q.js} +1 -1
  247. package/core/built/admin/assets/{tabs-CGisSbWq.js → tabs-D2yWLMp_.js} +1 -1
  248. package/core/built/admin/assets/tag-detail-BhXlOsCa.js +2 -0
  249. package/core/built/admin/assets/tags-ldukRApe.js +1 -0
  250. package/core/built/admin/assets/testimonials-modal-CtnZBJKl.js +1 -0
  251. package/core/built/admin/assets/{text-DU9Hv33_.js → text-BQDSDsKs.js} +1 -1
  252. package/core/built/admin/assets/{textarea-d9qEkMOP.js → textarea-DJ1Q5V39.js} +1 -1
  253. package/core/built/admin/assets/tier-detail-modal-BOs8e_G8.js +1 -0
  254. package/core/built/admin/assets/{toggle-group-CtKZURJd.js → toggle-group-ByUJsxwD.js} +1 -1
  255. package/core/built/admin/assets/{topic-filter-D8JT8Tq1.js → topic-filter-BgC3g5_v.js} +1 -1
  256. package/core/built/admin/assets/{transistor-modal-6PUf0idx.js → transistor-modal-Drr0SNfo.js} +1 -1
  257. package/core/built/admin/assets/trend-badge-6ye93YAW.js +1 -0
  258. package/core/built/admin/assets/{unsplash-modal-BPuDBPja.js → unsplash-modal-p6AoQoWp.js} +1 -1
  259. package/core/built/admin/assets/{use-analytics-data--opuzwcz.js → use-analytics-data-CWCMAESo.js} +1 -1
  260. package/core/built/admin/assets/use-check-theme-limit-error-B8bjF-gO.css +1 -0
  261. package/core/built/admin/assets/use-check-theme-limit-error-Cw3WQHy8.js +2 -0
  262. package/core/built/admin/assets/use-filter-params-BIni9taJ.js +1 -0
  263. package/core/built/admin/assets/use-growth-stats-DCVG3s9w.js +1 -0
  264. package/core/built/admin/assets/{use-limiter-TDSNiHPB.js → use-limiter-D2WRRx0H.js} +1 -1
  265. package/core/built/admin/assets/{use-navigate-with-base-path-CK989MlC.js → use-navigate-with-base-path-BHPA228f.js} +1 -1
  266. package/core/built/admin/assets/{use-pintura-config-CnCGrDFk.js → use-pintura-config-h-J0Lvta.js} +1 -1
  267. package/core/built/admin/assets/{use-pintura-editor-ntphvtUd.js → use-pintura-editor-BfN1UwzH.js} +1 -1
  268. package/core/built/admin/assets/use-post-referrers-Czg47m8s.js +1 -0
  269. package/core/built/admin/assets/use-reply-chain-data-ns0caK8k.js +1 -0
  270. package/core/built/admin/assets/{use-router-history-entry-BwfQS2mq.js → use-router-history-entry-uldn7zJO.js} +1 -1
  271. package/core/built/admin/assets/{use-setting-group-DQxV42-O.js → use-setting-group-DpcuCUDg.js} +1 -1
  272. package/core/built/admin/assets/use-settings-navigation-CZjGrhsp.js +1 -0
  273. package/core/built/admin/assets/{use-simple-pagination-B2TdNX67.js → use-simple-pagination-DzJWobPR.js} +1 -1
  274. package/core/built/admin/assets/{use-staff-users-QrR9voeC.js → use-staff-users-Csipb-O0.js} +1 -1
  275. package/core/built/admin/assets/use-tinybird-query-CSnGXzZh.js +1 -0
  276. package/core/built/admin/assets/use-tinybird-token-DTOKRR7e.js +1 -0
  277. package/core/built/admin/assets/{use-unsaved-changes-guard-DpYJcj1d.js → use-unsaved-changes-guard-CBzs-KPi.js} +1 -1
  278. package/core/built/admin/assets/{use-url-input-WnIqckzS.js → use-url-input-DFr8lCbS.js} +1 -1
  279. package/core/built/admin/assets/user-detail-modal-B_iUClI6.js +1 -0
  280. package/core/built/admin/assets/users-Cg-OeGzC.js +1 -0
  281. package/core/built/admin/assets/{virtual-list-D3MfPNOh.js → virtual-list-BW-padMe.js} +1 -1
  282. package/core/built/admin/assets/web-C1v33ehA.js +2 -0
  283. package/core/built/admin/assets/web-C4BKzn2T.js +1 -0
  284. package/core/built/admin/assets/{zapier-modal-BO6PS_Vd.js → zapier-modal-BBFfI6Ml.js} +1 -1
  285. package/core/built/admin/index.html +24 -30
  286. package/core/frontend/apps/private-blogging/lib/views/private.hbs +44 -43
  287. package/core/frontend/helpers/cancel_link.js +2 -2
  288. package/core/frontend/helpers/pagination.js +2 -2
  289. package/core/frontend/helpers/social_accounts.js +2 -2
  290. package/core/frontend/public/ghost.css +1 -1
  291. package/core/frontend/public/ghost.min.css +1 -1
  292. package/core/frontend/services/rendering/renderer.js +15 -0
  293. package/core/frontend/services/sitemap/user-map-generator.js +9 -2
  294. package/core/server/adapters/storage/LocalStorageBase.js +8 -5
  295. package/core/server/api/endpoints/db.js +19 -5
  296. package/core/server/api/endpoints/index.js +6 -2
  297. package/core/server/api/endpoints/mail.js +1 -1
  298. package/core/server/api/endpoints/member-metafields.js +89 -0
  299. package/core/server/api/endpoints/members.js +4 -4
  300. package/core/server/api/endpoints/posts.js +4 -3
  301. package/core/server/api/endpoints/tiers-checkout-config.js +59 -0
  302. package/core/server/api/endpoints/utils/serializers/input/members.js +4 -7
  303. package/core/server/api/endpoints/utils/serializers/output/config.js +1 -0
  304. package/core/server/api/endpoints/utils/serializers/output/index.js +6 -2
  305. package/core/server/api/endpoints/utils/serializers/output/{member-custom-fields.js → member-metafields.js} +2 -4
  306. package/core/server/api/endpoints/utils/serializers/output/members.js +2 -4
  307. package/core/server/api/endpoints/utils/serializers/output/tiers-checkout-config.js +12 -0
  308. package/core/server/api/endpoints/utils/validators/input/invitations.js +0 -5
  309. package/core/server/api/endpoints/utils/validators/input/pages.js +1 -1
  310. package/core/server/api/endpoints/utils/validators/input/posts.js +1 -1
  311. package/core/server/data/exporter/table-lists.js +3 -0
  312. package/core/server/data/importer/content-files.js +57 -0
  313. package/core/server/data/importer/handlers/image.js +2 -2
  314. package/core/server/data/importer/import-archive.js +131 -0
  315. package/core/server/data/importer/import-manager.js +18 -180
  316. package/core/server/data/importer/importers/content-file-importer.js +66 -4
  317. package/core/server/data/importer/importers/data/data-importer.js +3 -3
  318. package/core/server/data/migrations/utils/migrations.js +1 -1
  319. package/core/server/data/migrations/versions/6.61/2026-08-19-14-30-20-add-written-by-to-members-custom-field-values.js +42 -0
  320. package/core/server/data/migrations/versions/6.61/2026-08-19-14-38-53-add-members-custom-field-bindings-table.js +26 -0
  321. package/core/server/data/migrations/versions/6.61/2026-08-19-14-48-06-add-tier-checkout-field-tables.js +35 -0
  322. package/core/server/data/schema/schema.js +90 -0
  323. package/core/server/data/seeders/importers/automation-action-revisions-importer.js +3 -3
  324. package/core/server/data/seeders/importers/automation-actions-importer.js +3 -3
  325. package/core/server/data/seeders/importers/automation-run-steps-importer.js +3 -3
  326. package/core/server/data/seeders/importers/automation-runs-importer.js +5 -5
  327. package/core/server/data/seeders/importers/automations-importer.js +3 -3
  328. package/core/server/data/seeders/importers/comment-reports-importer.js +1 -1
  329. package/core/server/data/seeders/importers/comments-importer.js +1 -1
  330. package/core/server/data/seeders/importers/email-batches-importer.js +1 -1
  331. package/core/server/data/seeders/importers/email-recipients-importer.js +1 -1
  332. package/core/server/data/seeders/importers/emails-importer.js +1 -1
  333. package/core/server/data/seeders/importers/labels-importer.js +1 -1
  334. package/core/server/data/seeders/importers/members-click-events-importer.js +1 -1
  335. package/core/server/data/seeders/importers/members-created-events-importer.js +1 -1
  336. package/core/server/data/seeders/importers/members-feedback-importer.js +1 -1
  337. package/core/server/data/seeders/importers/members-importer.js +1 -1
  338. package/core/server/data/seeders/importers/members-login-events-importer.js +1 -1
  339. package/core/server/data/seeders/importers/members-products-importer.js +1 -1
  340. package/core/server/data/seeders/importers/members-status-events-importer.js +1 -1
  341. package/core/server/data/seeders/importers/members-stripe-customers-importer.js +1 -1
  342. package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +1 -1
  343. package/core/server/data/seeders/importers/members-subscribe-events-importer.js +1 -1
  344. package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +1 -1
  345. package/core/server/data/seeders/importers/offer-redemptions-importer.js +1 -1
  346. package/core/server/data/seeders/importers/offers-importer.js +1 -1
  347. package/core/server/data/seeders/importers/posts-importer.js +1 -1
  348. package/core/server/data/seeders/importers/table-importer.js +2 -2
  349. package/core/server/data/seeders/importers/tags-importer.js +1 -1
  350. package/core/server/data/seeders/importers/users-importer.js +1 -1
  351. package/core/server/data/seeders/importers/web-mentions-importer.js +1 -1
  352. package/core/server/data/seeders/utils/random.js +3 -3
  353. package/core/server/ghost-server.js +3 -2
  354. package/core/server/lib/db-types/boolean.js +18 -0
  355. package/core/server/lib/image/image-size.js +24 -0
  356. package/core/server/lib/package-json/parse.js +2 -2
  357. package/core/server/lib/promise-pool.js +31 -0
  358. package/core/server/models/member.js +6 -17
  359. package/core/server/models/post.js +2 -2
  360. package/core/server/models/relations/authors.js +2 -16
  361. package/core/server/models/user.js +9 -2
  362. package/core/server/services/auth/passwordreset.js +13 -0
  363. package/core/server/services/automations/database-automations-repository.js +26 -26
  364. package/core/server/services/automations/poll.js +1 -0
  365. package/core/server/services/comments/comments-controller.js +30 -9
  366. package/core/server/services/comments/comments-service.js +55 -5
  367. package/core/server/services/content-import/csv/formula.js +12 -0
  368. package/core/server/services/content-import/csv/index.js +6 -1
  369. package/core/server/services/content-import/csv/parse.js +27 -6
  370. package/core/server/services/content-import/csv/serialize.js +13 -0
  371. package/core/server/services/content-import/import/assets.js +74 -0
  372. package/core/server/services/content-import/import/completion-email.js +164 -0
  373. package/core/server/services/content-import/import/errors-file.js +36 -0
  374. package/core/server/services/content-import/import/importer.js +204 -54
  375. package/core/server/services/content-import/import/local-media-url.js +63 -0
  376. package/core/server/services/content-import/import/media.js +254 -0
  377. package/core/server/services/content-import/import/post-data.js +1 -1
  378. package/core/server/services/content-import/import/post-link.js +11 -0
  379. package/core/server/services/content-import/import/post-repository.js +100 -0
  380. package/core/server/services/content-import/import/reader.js +9 -2
  381. package/core/server/services/content-import/import/relations.js +149 -0
  382. package/core/server/services/content-import/import/report.js +34 -0
  383. package/core/server/services/content-import/import/row.js +11 -2
  384. package/core/server/services/content-import/import/schema.js +30 -0
  385. package/core/server/services/content-import/import/source.js +125 -0
  386. package/core/server/services/content-import/import/staged-file.js +29 -0
  387. package/core/server/services/content-import/import/store.js +25 -2
  388. package/core/server/services/content-import/index.js +58 -34
  389. package/core/server/services/content-import/jobs/content-csv-import-job.js +20 -0
  390. package/core/server/services/custom-redirects/redirect-config-parser.js +2 -2
  391. package/core/server/services/custom-redirects/redirects-service.js +1 -1
  392. package/core/server/services/custom-redirects/validation.js +1 -1
  393. package/core/server/services/email-address/email-address-parser.js +30 -38
  394. package/core/server/services/email-address/email-address-service.js +37 -4
  395. package/core/server/services/email-analytics/email-analytics-service.js +8 -1
  396. package/core/server/services/email-analytics/index.js +2 -7
  397. package/core/server/services/email-analytics/lib/queries.js +7 -1
  398. package/core/server/services/email-service/batch-sending-service.js +0 -4
  399. package/core/server/services/email-service/email-renderer.js +14 -4
  400. package/core/server/services/email-service/email-service-wrapper.js +0 -9
  401. package/core/server/services/email-service/mailgun-email-provider.js +20 -9
  402. package/core/server/services/gift-links/schema.js +3 -3
  403. package/core/server/services/gifts/CONTEXT.md +1 -1
  404. package/core/server/services/gifts/constants.js +1 -1
  405. package/core/server/services/gifts/email-templates/gift-delivery.hbs +3 -3
  406. package/core/server/services/gifts/gift-bookshelf-repository.js +2 -2
  407. package/core/server/services/gifts/gift-delivery-bookshelf-repository.js +18 -9
  408. package/core/server/services/gifts/gift-delivery-schema.js +5 -5
  409. package/core/server/services/gifts/gift-delivery-service.js +4 -0
  410. package/core/server/services/gifts/gift-email-service.js +0 -1
  411. package/core/server/services/gifts/gift-schema.js +10 -10
  412. package/core/server/services/gifts/gift-service.js +4 -2
  413. package/core/server/services/gifts/index.js +3 -3
  414. package/core/server/services/gifts/jobs/index.js +4 -4
  415. package/core/server/services/indexnow-ping/indexnow-ping-service.js +2 -2
  416. package/core/server/services/invitations/accept.js +4 -2
  417. package/core/server/services/jobs/job-service.js +0 -9
  418. package/core/server/services/jobs-service/register-job-handlers.js +25 -15
  419. package/core/server/services/lib/mailgun-client.js +44 -8
  420. package/core/server/services/mail/ghost-mailer.js +4 -4
  421. package/core/server/services/media-inliner/external-media-inliner-job.js +12 -0
  422. package/core/server/services/media-inliner/external-media-inliner.js +103 -29
  423. package/core/server/services/media-inliner/service.js +13 -41
  424. package/core/server/services/media-inliner/types.js +2 -0
  425. package/core/server/services/member-welcome-emails/service.js +7 -7
  426. package/core/server/services/members/api.js +1 -0
  427. package/core/server/services/members/import-export/csv/parse.js +0 -7
  428. package/core/server/services/members/import-export/export/exporter.js +4 -6
  429. package/core/server/services/members/import-export/import/completion-email.js +2 -5
  430. package/core/server/services/members/import-export/import/importer.js +21 -5
  431. package/core/server/services/members/import-export/import/row.js +0 -3
  432. package/core/server/services/members/import-export/import/stripe-utils.js +6 -1
  433. package/core/server/services/members/import-export/index.js +10 -12
  434. package/core/server/services/members/jobs/clean-expired-comped-job.js +7 -0
  435. package/core/server/services/members/jobs/clean-expired-comped-task.js +145 -0
  436. package/core/server/services/members/jobs/index.js +48 -24
  437. package/core/server/services/members/members-api/controllers/member-controller.js +11 -0
  438. package/core/server/services/members/members-api/controllers/router-controller.js +3 -6
  439. package/core/server/services/members/members-api/members-api.js +9 -6
  440. package/core/server/services/members/members-api/repositories/event-repository.js +0 -4
  441. package/core/server/services/members/members-api/services/member-bread-service.js +53 -63
  442. package/core/server/services/members/members-api/services/payments-service.js +47 -1
  443. package/core/server/services/members/service.js +1 -5
  444. package/core/server/services/members-custom-fields/actions.js +27 -1
  445. package/core/server/services/members-custom-fields/bindings-service.js +120 -0
  446. package/core/server/services/members-custom-fields/codec.js +3 -4
  447. package/core/server/services/members-custom-fields/definitions-service.js +165 -22
  448. package/core/server/services/members-custom-fields/filter.js +75 -93
  449. package/core/server/services/members-custom-fields/index.js +11 -4
  450. package/core/server/services/members-custom-fields/key.js +2 -1
  451. package/core/server/services/members-custom-fields/models.js +1 -1
  452. package/core/server/services/members-custom-fields/queries.js +5 -0
  453. package/core/server/services/members-custom-fields/schema.js +37 -6
  454. package/core/server/services/members-custom-fields/serializers.js +3 -5
  455. package/core/server/services/members-custom-fields/values-service.js +65 -31
  456. package/core/server/services/mentions/mention-controller.js +23 -21
  457. package/core/server/services/mentions/process-webmention-job.js +16 -0
  458. package/core/server/services/mentions/service.js +8 -4
  459. package/core/server/services/public-config/config.js +7 -0
  460. package/core/server/services/route-settings/validation-errors.js +1 -1
  461. package/core/server/services/route-settings/yaml-parser.js +1 -1
  462. package/core/server/services/settings/settings-service.js +1 -8
  463. package/core/server/services/settings-helpers/settings-helpers.js +4 -6
  464. package/core/server/services/slack-ping/slack-ping-service.js +1 -1
  465. package/core/server/services/staff/staff-service-emails.js +2 -2
  466. package/core/server/services/stripe/config.js +14 -8
  467. package/core/server/services/stripe/service.js +10 -1
  468. package/core/server/services/stripe/services/checkout/completed-session.js +68 -0
  469. package/core/server/services/stripe/services/checkout/session-options.js +100 -0
  470. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +71 -1
  471. package/core/server/services/stripe/stripe-api.js +31 -0
  472. package/core/server/services/stripe/stripe-service.js +26 -9
  473. package/core/server/services/stripe/webhook-controller.js +10 -0
  474. package/core/server/services/tier-checkout-config/codec.js +99 -0
  475. package/core/server/services/tier-checkout-config/index.js +26 -0
  476. package/core/server/services/tier-checkout-config/models.js +63 -0
  477. package/core/server/services/tier-checkout-config/queries.js +80 -0
  478. package/core/server/services/tier-checkout-config/schema.js +33 -0
  479. package/core/server/services/tier-checkout-config/serializers.js +128 -0
  480. package/core/server/services/tier-checkout-config/service.js +304 -0
  481. package/core/server/services/tiers/service.js +21 -0
  482. package/core/server/services/update-check/index.js +13 -13
  483. package/core/server/services/update-check/jobs/update-check-job.js +7 -0
  484. package/core/server/services/update-check/update-check-service.js +2 -2
  485. package/core/server/web/admin/controller.js +1 -1
  486. package/core/server/web/api/endpoints/admin/routes.js +34 -20
  487. package/core/server/web/api/middleware/upload.js +2 -2
  488. package/core/server/web/parent/app.js +5 -0
  489. package/core/server/web/parent/middleware/filter-query-parameters.js +94 -0
  490. package/core/server/web/parent/middleware/index.js +1 -0
  491. package/core/server/web/query-parameter-policy/index.js +9 -0
  492. package/core/server/web/query-parameter-policy/policy.json +58 -0
  493. package/core/server/web/query-parameter-policy/schema.js +38 -0
  494. package/core/shared/config/defaults.json +3 -0
  495. package/core/shared/config/env/config.testing-mysql.json +4 -1
  496. package/core/shared/config/env/config.testing.json +3 -0
  497. package/core/shared/config/overrides.json +4 -2
  498. package/core/shared/labs.js +9 -15
  499. package/package.json +25 -25
  500. package/pnpm-lock.yaml +741 -108
  501. package/pnpm-workspace.yaml +12 -9
  502. package/tsconfig.json +2 -3
  503. package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
  504. package/core/built/admin/assets/Combination-gnJO3vIw.js +0 -41
  505. package/core/built/admin/assets/account-migration-8qTEI1i1.js +0 -1
  506. package/core/built/admin/assets/activitypub/inbox-DQ5CwLma.js +0 -1284
  507. package/core/built/admin/assets/activitypub/label-B72sfvEU.js +0 -16
  508. package/core/built/admin/assets/activitypub/layout-CXajhRsL.js +0 -1
  509. package/core/built/admin/assets/activitypub/settings-71gCm3SO.js +0 -315
  510. package/core/built/admin/assets/add-integration-modal--YpTq3c0.js +0 -1
  511. package/core/built/admin/assets/add-newsletter-modal-C8Ix2hol.js +0 -1
  512. package/core/built/admin/assets/add-recommendation-modal-CScuNFz4.js +0 -1
  513. package/core/built/admin/assets/alert-dialog-wEbVxANM.js +0 -1
  514. package/core/built/admin/assets/analytics-Cnji45H5.js +0 -1
  515. package/core/built/admin/assets/app-utils-D2Wp7ZSS.js +0 -1
  516. package/core/built/admin/assets/audience-DTLMXAFH.js +0 -1
  517. package/core/built/admin/assets/automations-BWpRCZ_f.js +0 -1
  518. package/core/built/admin/assets/bluesky-sharing-DHOpQxff.js +0 -1
  519. package/core/built/admin/assets/chart-helpers-B3OqQ6Ez.js +0 -1
  520. package/core/built/admin/assets/color-picker-Bmo1r9Fc.js +0 -3
  521. package/core/built/admin/assets/color-picker-field-DywowPhF.js +0 -1
  522. package/core/built/admin/assets/combobox-C4QCh6Yw.js +0 -1
  523. package/core/built/admin/assets/command-DcLbSifC.js +0 -1
  524. package/core/built/admin/assets/comments-Bxy_3WyE.js +0 -1
  525. package/core/built/admin/assets/confirmation-context-CLFHhyyH.js +0 -1
  526. package/core/built/admin/assets/custom-field-type-option-D0PXoSnP.js +0 -10
  527. package/core/built/admin/assets/custom-integration-modal-DCIHKN33.js +0 -1
  528. package/core/built/admin/assets/design-and-theme-modal-CZ-PkW7o.js +0 -23
  529. package/core/built/admin/assets/dialog-Db7c164G.js +0 -1
  530. package/core/built/admin/assets/dirty-confirm-dialog-CmARpZQS.js +0 -1
  531. package/core/built/admin/assets/dist-QLZuGq7o2.js +0 -1
  532. package/core/built/admin/assets/domain-D0SAtc-T.js +0 -1
  533. package/core/built/admin/assets/dropzone-CaPOeDRZ.js +0 -5
  534. package/core/built/admin/assets/edit-profile-By7v5XW5.js +0 -1
  535. package/core/built/admin/assets/editor-COXn4Atu.js +0 -8
  536. package/core/built/admin/assets/explore-BsNYrubV.js +0 -1
  537. package/core/built/admin/assets/feed-8RR1O1bK.js +0 -1
  538. package/core/built/admin/assets/filter-sources-4C0kF1NU.js +0 -1
  539. package/core/built/admin/assets/filters-CV5jCb9n.js +0 -1
  540. package/core/built/admin/assets/filters-Dmr0PoqU.js +0 -1
  541. package/core/built/admin/assets/first-promoter-modal-OCzSaL3B.js +0 -1
  542. package/core/built/admin/assets/gift-link-modal-0mzjn5oF.js +0 -1
  543. package/core/built/admin/assets/growth-55vWlGT_.js +0 -1
  544. package/core/built/admin/assets/growth-DA5AluYw.js +0 -3
  545. package/core/built/admin/assets/history-modal-G1s5hHz7.js +0 -1
  546. package/core/built/admin/assets/html-field-BegxsWpw.js +0 -1
  547. package/core/built/admin/assets/index-CDP9tIOk.js +0 -4
  548. package/core/built/admin/assets/index-Dqf2x-ry.css +0 -1
  549. package/core/built/admin/assets/index.es-BMUCj9TR.js +0 -1
  550. package/core/built/admin/assets/input-UHrKuvBT.js +0 -1
  551. package/core/built/admin/assets/invite-user-modal-BrAkxEO3.js +0 -1
  552. package/core/built/admin/assets/koenig-loader-DwVY_V-H.js +0 -1
  553. package/core/built/admin/assets/kpi-card-LERqWBoY.js +0 -1
  554. package/core/built/admin/assets/kpi-helpers-DB3ouhYF.js +0 -1
  555. package/core/built/admin/assets/kpi-tabs-BGvtMD8S.js +0 -1
  556. package/core/built/admin/assets/label-picker-BBWykUeA.js +0 -1
  557. package/core/built/admin/assets/layout-Di5K7doM.js +0 -1
  558. package/core/built/admin/assets/loading-indicator-SFTqyYVE.js +0 -1
  559. package/core/built/admin/assets/member-custom-fields-gOv6Hoh3.js +0 -1
  560. package/core/built/admin/assets/member-detail-D7mZDDp3.js +0 -1
  561. package/core/built/admin/assets/member-route-0Aa52Ntz.js +0 -1
  562. package/core/built/admin/assets/members-DvZG9t69.js +0 -2
  563. package/core/built/admin/assets/moderation-DzzPLDyl.js +0 -1
  564. package/core/built/admin/assets/navigation-modal-CI8Bx5vn.js +0 -1
  565. package/core/built/admin/assets/newsletter-Bg3cq5U-.js +0 -1
  566. package/core/built/admin/assets/newsletter-detail-modal-DQU3r4g2.js +0 -1
  567. package/core/built/admin/assets/newsletters-DMDfZbMS.js +0 -1
  568. package/core/built/admin/assets/note-i8k6pjaw.js +0 -1
  569. package/core/built/admin/assets/notifications-psA_2Ph4.js +0 -1
  570. package/core/built/admin/assets/offers-container-modal-BWfjSdyg.js +0 -1
  571. package/core/built/admin/assets/onboarding-DGQuV7Ew.js +0 -1
  572. package/core/built/admin/assets/overview-C-6xOLaf.js +0 -1
  573. package/core/built/admin/assets/overview-DaLhpRrC.js +0 -1
  574. package/core/built/admin/assets/pages-B6vr9F_D.js +0 -1
  575. package/core/built/admin/assets/popover-CFgqFDvC.js +0 -1
  576. package/core/built/admin/assets/portal-frame-DjBF-uy2.js +0 -1
  577. package/core/built/admin/assets/portal-modal-Cf0pBAk8.js +0 -1
  578. package/core/built/admin/assets/post-analytics-Buz1COoY.js +0 -1
  579. package/core/built/admin/assets/post-analytics-header-B-P0eFnT.js +0 -1
  580. package/core/built/admin/assets/posts-KY3aUhVV.js +0 -1
  581. package/core/built/admin/assets/preferences-Cq1aOiLy.js +0 -5
  582. package/core/built/admin/assets/profile-D6dHYf2o.js +0 -1
  583. package/core/built/admin/assets/select-D_RQaXgt.js +0 -1
  584. package/core/built/admin/assets/settings-B5hJKZ5C.js +0 -1
  585. package/core/built/admin/assets/settings-DIo_F-MF.js +0 -4
  586. package/core/built/admin/assets/share-modal-CLBfj377.js +0 -1
  587. package/core/built/admin/assets/shim-ClF_m5ux.js +0 -1
  588. package/core/built/admin/assets/slack-modal-Bt9I_tEo.js +0 -1
  589. package/core/built/admin/assets/sort-button-DMBXF7tP.js +0 -1
  590. package/core/built/admin/assets/stats-B_YkF7bp.js +0 -1
  591. package/core/built/admin/assets/stats-view-BOCp9RG8.js +0 -1
  592. package/core/built/admin/assets/stripe-connect-modal-BeSukI8q.js +0 -1
  593. package/core/built/admin/assets/tag-detail-2a3BfzTW.js +0 -2
  594. package/core/built/admin/assets/tags-Dp2_8UzL.js +0 -1
  595. package/core/built/admin/assets/testimonials-modal-C9nrypNo.js +0 -1
  596. package/core/built/admin/assets/tier-detail-modal-CxRwgTPR.js +0 -1
  597. package/core/built/admin/assets/use-check-theme-limit-error-C9-QhGdV.js +0 -1
  598. package/core/built/admin/assets/use-filter-params-CSpAvgD2.js +0 -1
  599. package/core/built/admin/assets/use-growth-stats-BOmoPDHJ.js +0 -1
  600. package/core/built/admin/assets/use-post-referrers-CbXLFIex.js +0 -1
  601. package/core/built/admin/assets/use-reply-chain-data-JYFKSO83.js +0 -1
  602. package/core/built/admin/assets/use-settings-navigation-BBzro2-V.js +0 -1
  603. package/core/built/admin/assets/use-tinybird-query-D2Aeoty5.js +0 -39
  604. package/core/built/admin/assets/use-tinybird-token-DBuwMmln.js +0 -1
  605. package/core/built/admin/assets/user-detail-modal-59mBJqxN.js +0 -1
  606. package/core/built/admin/assets/users-5msE1WMs.js +0 -1
  607. package/core/built/admin/assets/web-DUuX7nE2.js +0 -2
  608. package/core/built/admin/assets/web-N8ucs2aG.js +0 -1
  609. package/core/server/api/endpoints/member-custom-fields.js +0 -100
  610. package/core/server/services/jobs/worker-model-event-bridge.js +0 -149
  611. package/core/server/services/members/jobs/clean-expired-comped.js +0 -132
  612. package/core/server/services/update-check/run-update-check.js +0 -63
  613. /package/core/server/lib/{db-date.js → db-types/date.js} +0 -0
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectedByPort = exports.CompletedSession = void 0;
4
+ const zod_1 = require("zod");
5
+ const checkout_1 = require("@tryghost/checkout");
6
+ /**
7
+ * What Ghost reads off a completed session.
8
+ *
9
+ * `shipping` is where the address lives at Ghost's pinned Stripe API version; later
10
+ * versions moved it to `collected_information.shipping_details`. An Event is an immutable
11
+ * snapshot rendered at the account's version, so nothing in a payload would warn us if
12
+ * that pin ever changed.
13
+ *
14
+ * A tax number is deliberately not read: Stripe keeps it against the customer it invoices
15
+ * and Ghost never stores one.
16
+ */
17
+ const Collected = zod_1.z
18
+ .string()
19
+ .nullish()
20
+ .transform((given) => (given && given.trim() !== '' ? given : undefined));
21
+ exports.CompletedSession = zod_1.z
22
+ .object({
23
+ custom_fields: zod_1.z
24
+ .array(zod_1.z
25
+ .object({
26
+ key: zod_1.z.string().nullish(),
27
+ text: zod_1.z.object({ value: Collected }).nullish(),
28
+ })
29
+ .nullable())
30
+ .nullish(),
31
+ shipping: zod_1.z
32
+ .object({
33
+ name: Collected,
34
+ address: zod_1.z.record(zod_1.z.string(), Collected).nullish(),
35
+ })
36
+ .nullish(),
37
+ customer_details: zod_1.z.object({ phone: Collected }).nullish(),
38
+ })
39
+ .loose();
40
+ /** Our address type needs at least one part; an empty value would erase what is there. */
41
+ function addressValue(address) {
42
+ const value = Object.fromEntries(Object.entries(address ?? {}).filter((entry) => entry[1] !== undefined));
43
+ return Object.keys(value).length > 0 ? value : undefined;
44
+ }
45
+ const PORT_VALUES = {
46
+ // Stripe returns the recipient's name alongside the address rather than as part of it,
47
+ // while Ghost keeps a name and an address in two separate custom fields. So the name is
48
+ // carried back separately here, under its own name, to be routed to its own field.
49
+ shipping_name: (session) => session.shipping?.name,
50
+ shipping_address: (session) => addressValue(session.shipping?.address),
51
+ phone: (session) => session.customer_details?.phone,
52
+ };
53
+ /**
54
+ * Everything a completed checkout gives back, as a list of pairs: the name Stripe used for
55
+ * a value, and the value itself. Answers to questions and values Stripe collected on its
56
+ * own both come back this way, because both are saved by the same route afterwards.
57
+ *
58
+ * Order matters. The values Stripe collected come last, so that if a question's answer and
59
+ * a collected value are both saved into the same custom field, the collected value is the
60
+ * one the field ends up holding.
61
+ */
62
+ exports.collectedByPort = exports.CompletedSession.transform((session) => {
63
+ const answers = (session.custom_fields ?? [])
64
+ .filter((field) => Boolean(field?.key && field.text?.value))
65
+ .map((field) => ({ port: field.key, value: field.text.value }));
66
+ const ports = checkout_1.STRIPE_PORTS.map((port) => ({ port, value: PORT_VALUES[port](session) })).filter((entry) => entry.value !== undefined);
67
+ return [...answers, ...ports];
68
+ });
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.stripeCheckoutCollectionOptions = stripeCheckoutCollectionOptions;
7
+ const logging_1 = __importDefault(require("@tryghost/logging"));
8
+ const checkout_1 = require("@tryghost/checkout");
9
+ /** Keyed on the eligible types, so this and the configure-time rule cannot drift apart. */
10
+ const QUESTION_TYPES = {
11
+ short_text: 'text',
12
+ };
13
+ function askable(question) {
14
+ if (!(0, checkout_1.isCheckoutEligible)(question.type)) {
15
+ logging_1.default.warn({
16
+ event: { name: 'stripe.checkout.question_skipped' },
17
+ customFieldKey: question.key,
18
+ fieldType: question.type,
19
+ reason: 'unsupported_type',
20
+ }, 'Skipping a Stripe checkout question');
21
+ return false;
22
+ }
23
+ if (question.prompt.length > checkout_1.MAX_CHECKOUT_LABEL_LENGTH) {
24
+ logging_1.default.warn({
25
+ event: { name: 'stripe.checkout.question_skipped' },
26
+ customFieldKey: question.key,
27
+ promptLength: question.prompt.length,
28
+ reason: 'label_too_long',
29
+ }, 'Skipping a Stripe checkout question');
30
+ return false;
31
+ }
32
+ return true;
33
+ }
34
+ /**
35
+ * Build the collection parameters for a checkout, or nothing at all.
36
+ *
37
+ * Returns an object with no keys when a tier asks for nothing, so a caller can spread it
38
+ * over its session options unconditionally and change nothing.
39
+ */
40
+ function stripeCheckoutCollectionOptions(checkout) {
41
+ const options = {};
42
+ if (!checkout) {
43
+ return options;
44
+ }
45
+ const eligible = checkout.customFields.filter(askable);
46
+ const questions = eligible.slice(0, checkout_1.MAX_CHECKOUT_CUSTOM_FIELDS);
47
+ if (questions.length < eligible.length) {
48
+ logging_1.default.warn({
49
+ event: { name: 'stripe.checkout.questions_trimmed' },
50
+ asked: questions.length,
51
+ configured: eligible.length,
52
+ }, 'Some Stripe checkout questions were not asked');
53
+ }
54
+ if (questions.length > 0) {
55
+ options.custom_fields = questions.map((question) => ({
56
+ // Ghost sends the custom field's own key as the question's identifier, and Stripe
57
+ // returns the buyer's answer labelled with that same key. Using it in both places
58
+ // means reading an answer is a direct lookup of the field it belongs to, with
59
+ // nothing in between that could map it to the wrong one.
60
+ key: question.key,
61
+ label: { type: 'custom', custom: question.prompt },
62
+ type: QUESTION_TYPES[question.type],
63
+ optional: question.optional,
64
+ }));
65
+ }
66
+ if (checkout.shipping) {
67
+ // Ghost stores "everywhere" as no list, because the set of countries moves and a
68
+ // stored copy of it would quietly become a restriction. Stripe has no such sentinel:
69
+ // `allowed_countries` is the only key `shipping_address_collection` has, so a request
70
+ // that omits it carries no parameter at all, and Stripe accepts it precisely because
71
+ // it was never asked to collect anything — a session that succeeds and collects no
72
+ // address. So everywhere is expanded to every country here, at the one point that
73
+ // builds the request.
74
+ //
75
+ // An empty list is neither everywhere nor a real restriction, and would encode to the
76
+ // same absent parameter. Defended here rather than trusted from the settings screen:
77
+ // this is the checkout path, and a malformed configuration must cost the collection
78
+ // rather than throw inside a session build.
79
+ const allowedCountries = checkout.shipping.allowedCountries ?? [...checkout_1.STRIPE_ALLOWED_COUNTRIES];
80
+ if (allowedCountries.length === 0) {
81
+ logging_1.default.warn({
82
+ event: { name: 'stripe.checkout.collection_skipped' },
83
+ port: 'shipping_address',
84
+ reason: 'no_allowed_countries',
85
+ }, 'Skipping a Stripe checkout collection');
86
+ }
87
+ else {
88
+ options.shipping_address_collection = { allowed_countries: allowedCountries };
89
+ }
90
+ }
91
+ // Unioned with whatever automatic tax asks for. Both want the same thing, so a site
92
+ // running the 2024 tax beta keeps collecting and a site that asked for it starts.
93
+ if (checkout.taxNumber) {
94
+ options.tax_id_collection = { enabled: true };
95
+ }
96
+ if (checkout.phone) {
97
+ options.phone_number_collection = { enabled: true };
98
+ }
99
+ return options;
100
+ }
@@ -1,7 +1,11 @@
1
1
  const _ = require('lodash');
2
2
  const errors = require('@tryghost/errors');
3
3
  const logging = require('@tryghost/logging');
4
- const { canWelcomeEmailReplaceSignupPaidEmail } = require('../../../lib/member-signup-contexts');
4
+ const {
5
+ SIGNUP_CONTEXTS,
6
+ canWelcomeEmailReplaceSignupPaidEmail,
7
+ } = require('../../../lib/member-signup-contexts');
8
+ const { collectedByPort } = require('../checkout/completed-session');
5
9
  /** @typedef {import('../../../lib/member-signup-contexts').SignupContext} SignupContext */
6
10
 
7
11
  function isStripeMetadataTrue(value) {
@@ -334,6 +338,7 @@ module.exports = class CheckoutSessionEventService {
334
338
  email: customer.email,
335
339
  });
336
340
 
341
+ const memberPreexisted = Boolean(member);
337
342
  const checkoutType = _.get(session, 'metadata.checkoutType');
338
343
 
339
344
  if (!member) {
@@ -422,6 +427,11 @@ module.exports = class CheckoutSessionEventService {
422
427
  }
423
428
  }
424
429
 
430
+ // After the subscription work, and deliberately not part of it: a value the member
431
+ // gave us for free must never be able to fail the webhook. A throw here would make
432
+ // Stripe retry the event and risk doing the payment work twice.
433
+ await this.writeCollectedFields(member.id, session, { memberPreexisted });
434
+
425
435
  if (checkoutType !== 'upgrade') {
426
436
  const ghostSignupContext = /** @type {SignupContext | undefined} */ (
427
437
  session.metadata?.ghostSignupContext
@@ -440,4 +450,64 @@ module.exports = class CheckoutSessionEventService {
440
450
  }
441
451
  }
442
452
  }
453
+
454
+ /**
455
+ * This service knows how to read a completed Stripe session. It does not know, and must
456
+ * not know, which custom field any of those values belongs in — that is what a binding
457
+ * decides, so no field key appears anywhere in this code.
458
+ *
459
+ * Nothing here may be fatal: a throw fails the webhook, which makes Stripe retry it and
460
+ * risks doing the payment work twice.
461
+ *
462
+ * @param {string} memberId
463
+ * @param {import('stripe').Stripe.Checkout.Session} session
464
+ * @param {object} options
465
+ * @param {boolean} options.memberPreexisted Whether the member's record existed before this event resolved it
466
+ */
467
+ async writeCollectedFields(memberId, session, { memberPreexisted }) {
468
+ // Stamped at create time. A session predating this feature carries none. Read
469
+ // outside the try so a failure below can name the tier whose answers were lost.
470
+ const tierId = session.metadata?.ghostTierId;
471
+
472
+ try {
473
+ if (!tierId) {
474
+ return;
475
+ }
476
+
477
+ // A checkout can be started with nothing but an email address, and typing an
478
+ // email is not proof of owning it. Values may land on the member this event just
479
+ // created — that record holds nothing the buyer didn't supply — but a record
480
+ // that existed before the checkout belongs to whoever verified that email, so it
481
+ // is only written when the session was created by a signed-in member.
482
+ const wasAuthenticated =
483
+ session.metadata?.ghostSignupContext === SIGNUP_CONTEXTS.ALREADY_AUTHENTICATED;
484
+ if (memberPreexisted && !wasAuthenticated) {
485
+ logging.warn(
486
+ {
487
+ event: { name: 'stripe_checkout.collected_fields.write_skipped' },
488
+ memberId,
489
+ tierId,
490
+ },
491
+ 'Skipped storing the fields an unverified checkout collected for an existing member',
492
+ );
493
+ return;
494
+ }
495
+
496
+ await this.deps.customFieldBindings.writeCollected(
497
+ memberId,
498
+ tierId,
499
+ collectedByPort.parse(session),
500
+ );
501
+ } catch (err) {
502
+ logging.error(
503
+ {
504
+ event: { name: 'stripe_checkout.collected_fields.write_failed' },
505
+ err,
506
+ memberId,
507
+ tierId,
508
+ },
509
+ 'Failed to store the fields a checkout collected',
510
+ );
511
+ }
512
+ }
443
513
  };
@@ -3,6 +3,7 @@ const debug = require('@tryghost/debug')('stripe');
3
3
  const ghostConfig = require('../../../shared/config');
4
4
  const stripe = require('stripe');
5
5
  const i18n = require('../i18n');
6
+ const { stripeCheckoutCollectionOptions } = require('./services/checkout/session-options');
6
7
 
7
8
  /* Stripe has the following rate limits:
8
9
  * - For most APIs, 100 read requests per second in live mode, 25 read requests per second in test mode
@@ -61,6 +62,8 @@ const MANAGED_PAYMENTS_DISABLED = { enabled: false };
61
62
  */
62
63
 
63
64
  module.exports = class StripeAPI {
65
+ static API_VERSION = STRIPE_API_VERSION;
66
+
64
67
  /**
65
68
  * StripeAPI
66
69
  * @param {object} deps
@@ -556,6 +559,8 @@ module.exports = class StripeAPI {
556
559
  * @param {string} options.customerEmail
557
560
  * @param {number} options.trialDays
558
561
  * @param {string} [options.coupon]
562
+ * @param {import('../tier-checkout-config').ResolvedCheckout} [options.checkout] What
563
+ * the tier's checkout asks for beyond the payment. Absent, or empty, adds nothing.
559
564
  *
560
565
  * @returns {Promise<ICheckoutSession>}
561
566
  */
@@ -636,6 +641,32 @@ module.exports = class StripeAPI {
636
641
  hasCustomer: Boolean(customerId),
637
642
  });
638
643
 
644
+ // Applied after automatic tax so the two agree about `tax_id_collection`: both ask
645
+ // for the same thing, and a site with either reason to collect gets it. Nothing is
646
+ // added for a tier that has configured nothing, so an unconfigured site's request
647
+ // is unchanged.
648
+ Object.assign(stripeSessionOptions, stripeCheckoutCollectionOptions(options.checkout));
649
+
650
+ // Stripe refuses to collect a tax id for a customer it may not rename:
651
+ //
652
+ // Tax ID collection requires updating business name on the customer. To enable tax
653
+ // ID collection for an existing customer, please set `customer_update[name]`.
654
+ //
655
+ // Measured against the live API rather than read — `pnpm --filter @tryghost/e2e
656
+ // stripe:probe` — because the same probe shows shipping and phone collection need no
657
+ // such thing. Every signed-in checkout carries a customer, so without this a tier
658
+ // that collects a tax number cannot be bought by an existing member.
659
+ //
660
+ // Merged rather than assigned: automatic tax sets this too, and whichever ran first
661
+ // must not lose its keys. Still never set without a customer, which is the shape
662
+ // that took the tax beta down.
663
+ if (stripeSessionOptions.tax_id_collection && customerId) {
664
+ stripeSessionOptions.customer_update = {
665
+ ...stripeSessionOptions.customer_update,
666
+ name: 'auto',
667
+ };
668
+ }
669
+
639
670
  const session = await this._stripe.checkout.sessions.create(stripeSessionOptions);
640
671
 
641
672
  return session;
@@ -10,6 +10,7 @@ const InvoiceEventService = require('./services/webhook/invoice-event-service');
10
10
  const CheckoutSessionEventService = require('./services/webhook/checkout-session-event-service');
11
11
  const ChargeRefundedEventService = require('./services/webhook/charge-refunded-event-service');
12
12
  const memberWelcomeEmailService = require('../member-welcome-emails/service');
13
+ const customFields = require('../members-custom-fields');
13
14
 
14
15
  /**
15
16
  * @typedef {object} IStripeServiceConfig
@@ -24,6 +25,7 @@ const memberWelcomeEmailService = require('../member-welcome-emails/service');
24
25
  * @prop {boolean} testEnv Whether this is a test environment
25
26
  * @prop {string} webhookSecret The Stripe webhook secret
26
27
  * @prop {string} webhookHandlerUrl The URL to handle Stripe webhooks
28
+ * @prop {boolean} [ephemeralWebhook] Whether the webhook endpoint is deleted on shutdown
27
29
  * @prop {string[]} webhookCustomerIgnoreList List of customer IDs for customer.subscription.updated webhook bypass
28
30
  * @prop {string} siteUrl The site URL for billing portal return URL
29
31
  */
@@ -133,6 +135,11 @@ module.exports = class StripeService {
133
135
  memberWelcomeEmailService.init();
134
136
  return memberWelcomeEmailService.api.isMemberWelcomeEmailActive('paid');
135
137
  },
138
+ // A getter because the custom field services are built during boot: reading the
139
+ // binding at construction would capture the empty value it had beforehand.
140
+ get customFieldBindings() {
141
+ return customFields.bindings;
142
+ },
136
143
  });
137
144
 
138
145
  const chargeRefundedEventService = new ChargeRefundedEventService({
@@ -155,6 +162,8 @@ module.exports = class StripeService {
155
162
  this.migrations = migrations;
156
163
  this.webhookController = webhookController;
157
164
  this.billingPortalManager = billingPortalManager;
165
+ /** @private */
166
+ this.ephemeralWebhook = false;
158
167
  }
159
168
 
160
169
  async connect() {
@@ -206,23 +215,31 @@ module.exports = class StripeService {
206
215
  webhookSecret: config.webhookSecret,
207
216
  webhookHandlerUrl: config.webhookHandlerUrl,
208
217
  });
218
+ this.ephemeralWebhook = config.ephemeralWebhook === true;
209
219
 
210
220
  this.billingPortalManager.configure({
211
221
  siteUrl: config.siteUrl,
212
222
  });
213
223
 
214
- // webhookManager.start() already self-guards: configure() above puts it in
215
- // 'local' mode whenever a webhookSecret is set, which is always true outside
216
- // production (config.js defaults it to DEFAULT_WEBHOOK_SECRET), so start()
217
- // returns immediately without touching Stripe. Only billingPortalManager
218
- // needs an explicit test-env skip in the test env there is no real Stripe
219
- // to register against, and the mock returns 500 for billing_portal/
220
- // configurations, so this network-registration call only error-logs on
221
- // every boot. Tests never need a registered portal configuration. Skip it
222
- // under test; prod and dev register exactly as before.
224
+ // webhookManager.start() registers a webhook URL with Stripe only when no webhook
225
+ // secret was supplied. Outside production config.js supplies a placeholder secret
226
+ // unless stripeRemoteWebhooks is set, so start() normally returns without touching
227
+ // Stripe. billingPortalManager has no such guard: in the test environment the mock
228
+ // Stripe answers its registration call with a 500 on every boot, and tests never
229
+ // need a registered portal, so skip it under test only.
223
230
  await this.webhookManager.start();
224
231
  if (!config.testEnv) {
225
232
  await this.billingPortalManager.start();
226
233
  }
227
234
  }
235
+
236
+ /**
237
+ * Removes the webhook registration from Stripe, but only when it was marked ephemeral.
238
+ * A production registration is kept and reused on the next boot.
239
+ */
240
+ async shutdown() {
241
+ if (this.ephemeralWebhook) {
242
+ await this.webhookManager.stop();
243
+ }
244
+ }
228
245
  };
@@ -1,4 +1,5 @@
1
1
  const logging = require('@tryghost/logging');
2
+ const StripeAPI = require('./stripe-api');
2
3
 
3
4
  module.exports = class WebhookController {
4
5
  /**
@@ -89,6 +90,15 @@ module.exports = class WebhookController {
89
90
  return res.end();
90
91
  }
91
92
 
93
+ // Stripe shapes each event for the API version of the URL it was sent to. A different
94
+ // version means this event did not come through the URL Ghost registered, and fields
95
+ // may not be where Ghost reads them.
96
+ if (event.api_version !== StripeAPI.API_VERSION) {
97
+ logging.error(
98
+ `Webhook ${event.type} was rendered at Stripe API version ${event.api_version}, Ghost expects ${StripeAPI.API_VERSION}`,
99
+ );
100
+ }
101
+
92
102
  const customerId = this.getEventCustomerId(event);
93
103
  if (this.shouldIgnoreEvent(event, customerId)) {
94
104
  logging.info(
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.questionRowCodec = exports.QuestionRow = exports.collectionRowCodec = exports.CollectionRow = exports.optionsCodec = void 0;
4
+ const zod_1 = require("zod");
5
+ const custom_field_types_1 = require("@tryghost/custom-field-types");
6
+ const boolean_1 = require("../../lib/db-types/boolean");
7
+ const schema_1 = require("./schema");
8
+ const models_1 = require("./models");
9
+ const SEPARATOR = ',';
10
+ exports.optionsCodec = zod_1.z.codec(schema_1.DbCheckoutOptions, models_1.CheckoutOptions, {
11
+ // An absent list is everywhere, so the column is null rather than a copy of every
12
+ // country. An empty list is neither, and stays representable on purpose: nothing writes
13
+ // one, and the session builder refuses to collect against it rather than asking the
14
+ // processor for an address form it would never render.
15
+ decode: (columns) => ({
16
+ shippingAllowedCountries: columns.shipping_allowed_countries === null
17
+ ? null
18
+ : splitList(columns.shipping_allowed_countries),
19
+ taxNumber: columns.tax_number_collect,
20
+ }),
21
+ encode: (options) => ({
22
+ shipping_allowed_countries: options.shippingAllowedCountries === null ? null : joinList(options.shippingAllowedCountries),
23
+ tax_number_collect: options.taxNumber,
24
+ }),
25
+ });
26
+ function splitList(stored) {
27
+ return stored.split(SEPARATOR).filter(Boolean);
28
+ }
29
+ function joinList(values) {
30
+ return [...new Set(values)].join(SEPARATOR);
31
+ }
32
+ exports.CollectionRow = zod_1.z.object({
33
+ product_id: zod_1.z.string(),
34
+ config_id: zod_1.z.string().nullable(),
35
+ shipping_allowed_countries: zod_1.z.string().nullable(),
36
+ tax_number_collect: boolean_1.DbBoolean.nullable(),
37
+ shipping_name_key: zod_1.z.string().nullable(),
38
+ shipping_name_collectable: zod_1.z.string().nullable(),
39
+ shipping_address_key: zod_1.z.string().nullable(),
40
+ shipping_address_collectable: zod_1.z.string().nullable(),
41
+ phone_key: zod_1.z.string().nullable(),
42
+ phone_collectable: zod_1.z.string().nullable(),
43
+ });
44
+ exports.collectionRowCodec = exports.CollectionRow.transform((row) => {
45
+ const collection = collectedInto(row);
46
+ return {
47
+ tierId: row.product_id,
48
+ configured: row.config_id !== null,
49
+ collection,
50
+ collecting: {
51
+ // Stripe collects the recipient's name and their address under one parameter, so it
52
+ // asks for both or neither. Archiving one of the two destinations is the publisher
53
+ // saying they no longer want that half: the step is still worth asking for while the
54
+ // other half can land, and whatever arrives for the archived one is dropped. Only
55
+ // archiving both leaves nothing worth asking for.
56
+ shipping: row.shipping_name_collectable === null && row.shipping_address_collectable === null
57
+ ? null
58
+ : collection.shipping,
59
+ taxNumber: collection.taxNumber,
60
+ phone: row.phone_collectable === null ? null : collection.phone,
61
+ },
62
+ };
63
+ });
64
+ exports.QuestionRow = zod_1.z.object({
65
+ product_id: zod_1.z.string(),
66
+ port: zod_1.z.string(),
67
+ label: schema_1.DbCheckoutQuestion.shape.label,
68
+ optional: schema_1.DbCheckoutQuestion.shape.optional,
69
+ question_name: zod_1.z.string().nullable(),
70
+ question_type: custom_field_types_1.FieldTypeSchema.nullable(),
71
+ });
72
+ exports.questionRowCodec = exports.QuestionRow.transform((row) => ({
73
+ tierId: row.product_id,
74
+ question: {
75
+ key: row.port,
76
+ label: row.label,
77
+ optional: row.optional,
78
+ },
79
+ askable: row.question_type === null
80
+ ? null
81
+ : { prompt: row.label ?? row.question_name ?? row.port, type: row.question_type },
82
+ }));
83
+ function collectedInto(row) {
84
+ const { shippingAllowedCountries, taxNumber } = zod_1.z.decode(exports.optionsCodec, {
85
+ shipping_allowed_countries: row.shipping_allowed_countries,
86
+ tax_number_collect: row.tax_number_collect ?? false,
87
+ });
88
+ return {
89
+ shipping: row.shipping_name_key !== null && row.shipping_address_key !== null
90
+ ? {
91
+ allowedCountries: shippingAllowedCountries,
92
+ nameCustomFieldKey: row.shipping_name_key,
93
+ addressCustomFieldKey: row.shipping_address_key,
94
+ }
95
+ : null,
96
+ taxNumber,
97
+ phone: row.phone_key === null ? null : { customFieldKey: row.phone_key },
98
+ };
99
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toCheckoutConfigResponse = exports.TierCheckoutConfig = exports.ShippingCollection = exports.ResolvedQuestion = exports.ResolvedCheckout = exports.PhoneCollection = exports.emptyCheckoutConfig = exports.CheckoutQuestion = exports.TierCheckoutConfigService = void 0;
4
+ /**
5
+ * What a tier's checkout asks and collects.
6
+ *
7
+ * A domain of its own rather than part of the Tier aggregate: a tier is loaded into memory
8
+ * once at boot, and these rows are read live because deleting a custom field cascades a
9
+ * question away without that repository ever seeing it.
10
+ *
11
+ * Constructed by the tiers service wrapper at boot, which already holds the collaborators
12
+ * this needs, rather than by an init() here — the custom field services are built before
13
+ * it, so both are ready by the time it runs.
14
+ */
15
+ var service_1 = require("./service");
16
+ Object.defineProperty(exports, "TierCheckoutConfigService", { enumerable: true, get: function () { return service_1.TierCheckoutConfigService; } });
17
+ var models_1 = require("./models");
18
+ Object.defineProperty(exports, "CheckoutQuestion", { enumerable: true, get: function () { return models_1.CheckoutQuestion; } });
19
+ Object.defineProperty(exports, "emptyCheckoutConfig", { enumerable: true, get: function () { return models_1.emptyCheckoutConfig; } });
20
+ Object.defineProperty(exports, "PhoneCollection", { enumerable: true, get: function () { return models_1.PhoneCollection; } });
21
+ Object.defineProperty(exports, "ResolvedCheckout", { enumerable: true, get: function () { return models_1.ResolvedCheckout; } });
22
+ Object.defineProperty(exports, "ResolvedQuestion", { enumerable: true, get: function () { return models_1.ResolvedQuestion; } });
23
+ Object.defineProperty(exports, "ShippingCollection", { enumerable: true, get: function () { return models_1.ShippingCollection; } });
24
+ Object.defineProperty(exports, "TierCheckoutConfig", { enumerable: true, get: function () { return models_1.TierCheckoutConfig; } });
25
+ var serializers_1 = require("./serializers");
26
+ Object.defineProperty(exports, "toCheckoutConfigResponse", { enumerable: true, get: function () { return serializers_1.toCheckoutConfigResponse; } });
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResolvedCheckout = exports.ResolvedQuestion = exports.CheckoutOptions = exports.emptyCheckoutConfig = exports.emptyCollection = exports.TierCheckoutConfig = exports.PhoneCollection = exports.ShippingCollection = exports.CheckoutQuestion = void 0;
4
+ const zod_1 = require("zod");
5
+ const custom_field_types_1 = require("@tryghost/custom-field-types");
6
+ exports.CheckoutQuestion = zod_1.z.object({
7
+ key: zod_1.z.string(),
8
+ label: zod_1.z.string().nullable(),
9
+ optional: zod_1.z.boolean(),
10
+ });
11
+ /**
12
+ * One toggle and two destinations: a processor returns the recipient and the address
13
+ * under one parameter, but a publisher keeps a name and an address in different fields.
14
+ */
15
+ exports.ShippingCollection = zod_1.z.object({
16
+ /**
17
+ * ISO 3166-1 alpha-2, or null for everywhere the processor ships.
18
+ *
19
+ * Null rather than a stored enumeration of every country, because that list moves: the
20
+ * day the processor adds one, a saved "everywhere" would silently be a restriction that
21
+ * excludes it, and nothing would say so.
22
+ */
23
+ allowedCountries: zod_1.z.array(zod_1.z.string()).nullable(),
24
+ nameCustomFieldKey: zod_1.z.string(),
25
+ addressCustomFieldKey: zod_1.z.string(),
26
+ });
27
+ exports.PhoneCollection = zod_1.z.object({
28
+ customFieldKey: zod_1.z.string(),
29
+ });
30
+ exports.TierCheckoutConfig = zod_1.z.object({
31
+ tierId: zod_1.z.string(),
32
+ customFields: zod_1.z.array(exports.CheckoutQuestion),
33
+ shipping: exports.ShippingCollection.nullable(),
34
+ /** Stripe keeps a tax number against the customer it invoices; Ghost never stores one. */
35
+ taxNumber: zod_1.z.boolean(),
36
+ phone: exports.PhoneCollection.nullable(),
37
+ });
38
+ const emptyCollection = () => ({
39
+ shipping: null,
40
+ taxNumber: false,
41
+ phone: null,
42
+ });
43
+ exports.emptyCollection = emptyCollection;
44
+ const emptyCheckoutConfig = (tierId) => ({
45
+ tierId,
46
+ customFields: [],
47
+ ...(0, exports.emptyCollection)(),
48
+ });
49
+ exports.emptyCheckoutConfig = emptyCheckoutConfig;
50
+ exports.CheckoutOptions = zod_1.z.object({
51
+ shippingAllowedCountries: zod_1.z.array(zod_1.z.string()).nullable(),
52
+ taxNumber: zod_1.z.boolean(),
53
+ });
54
+ exports.ResolvedQuestion = exports.CheckoutQuestion.extend({
55
+ prompt: zod_1.z.string(),
56
+ type: custom_field_types_1.FieldTypeSchema,
57
+ });
58
+ exports.ResolvedCheckout = zod_1.z.object({
59
+ customFields: zod_1.z.array(exports.ResolvedQuestion),
60
+ shipping: exports.ShippingCollection.nullable(),
61
+ taxNumber: zod_1.z.boolean(),
62
+ phone: exports.PhoneCollection.nullable(),
63
+ });