ghost 6.47.0 → 6.48.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 (372) hide show
  1. package/MigratorConfig.js +0 -1
  2. package/bin/create-migration.js +1 -1
  3. package/bin/generate-golden-email.js +0 -1
  4. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  5. package/core/boot.js +13 -7
  6. package/core/built/admin/assets/{PolarAngleAxis-CLluELUg.js → PolarAngleAxis-DufzOBEz.js} +1 -1
  7. package/core/built/admin/assets/{_baseAssignValue-Dz1iqdwD.js → _baseAssignValue-BjUm4QeA.js} +1 -1
  8. package/core/built/admin/assets/{a-large-small-BA74vHPp.js → a-large-small-D0RF_WS9.js} +1 -1
  9. package/core/built/admin/assets/{account-migration-B-NqdKuN.js → account-migration-B7n6ezrZ.js} +1 -1
  10. package/core/built/admin/assets/activitypub/{account-migration-ClmVrAXr.js → account-migration-BJeRnMCl.js} +3 -3
  11. package/core/built/admin/assets/activitypub/activitypub.js +1 -1
  12. package/core/built/admin/assets/activitypub/{at-sign-9F3qd4Nn.js → at-sign-BygVOKdj.js} +2 -2
  13. package/core/built/admin/assets/activitypub/{avatar-flipboard-jBHA13Ej.js → avatar-flipboard-CNcjI8lK.js} +2 -2
  14. package/core/built/admin/assets/activitypub/{bluesky-sharing-1irhvVtF.js → bluesky-sharing-mgBNeRfA.js} +4 -4
  15. package/core/built/admin/assets/activitypub/{copy-DXRvsGiK.js → copy-BMChXIQc.js} +2 -2
  16. package/core/built/admin/assets/activitypub/{deleted-feed-item-67VQJde4.js → deleted-feed-item-D-spb6V1.js} +2 -2
  17. package/core/built/admin/assets/activitypub/{edit-profile-BfpbEj98.js → edit-profile-CDQ3V8-c.js} +4 -4
  18. package/core/built/admin/assets/activitypub/{feed-DUrI7SPC.js → feed-Vqr3WU-V.js} +4 -4
  19. package/core/built/admin/assets/activitypub/{hash-CMtB10PN.js → hash-D7SSXVOg.js} +2 -2
  20. package/core/built/admin/assets/activitypub/{inbox-2xq8SJ7A.js → inbox-BHk5Dw27.js} +2 -2
  21. package/core/built/admin/assets/activitypub/{index-BT97xLpd.js → index-BejkeH-m.js} +2 -2
  22. package/core/built/admin/assets/activitypub/{index-DssHQAjK.js → index-BqL23J-m.js} +32 -32
  23. package/core/built/admin/assets/activitypub/{index-Ba6FXt8y.js → index-CHhvjNuG.js} +6 -6
  24. package/core/built/admin/assets/activitypub/{index-ERtz4Bvy.js → index-CZsNO9hA.js} +5 -5
  25. package/core/built/admin/assets/activitypub/{index-sgY7lLrg.js → index-CaIhRYzz.js} +3 -3
  26. package/core/built/admin/assets/activitypub/{index-D1EWoXrK.js → index-DA2MkQVu.js} +2 -2
  27. package/core/built/admin/assets/activitypub/{index-sudOvKcY.js → index-DHhFcmfu.js} +21 -21
  28. package/core/built/admin/assets/activitypub/{index-Ccighz1t.js → index-sh2bDSls.js} +4 -4
  29. package/core/built/admin/assets/activitypub/{label-KQSsSjmN.js → label-D80yhOIm.js} +2 -2
  30. package/core/built/admin/assets/activitypub/{moderation-B78if-r0.js → moderation-LJNmKBCi.js} +3 -3
  31. package/core/built/admin/assets/activitypub/{note-Bfr7ovn7.js → note-CyjKLcQ-.js} +4 -4
  32. package/core/built/admin/assets/activitypub/{reply-CehesW8Z.js → reply-BiiCFx7G.js} +2 -2
  33. package/core/built/admin/assets/activitypub/{separator-B_Ly_Qq8.js → separator-CY3PMmtI.js} +2 -2
  34. package/core/built/admin/assets/activitypub/{settings-D6R97p7v.js → settings-BQy6Gr7R.js} +3 -3
  35. package/core/built/admin/assets/activitypub/{step-1-Cl1YxgZ1.js → step-1-DCqRwgf3.js} +3 -3
  36. package/core/built/admin/assets/activitypub/{step-2-DDyR--Qe.js → step-2-DQlhehTO.js} +5 -5
  37. package/core/built/admin/assets/activitypub/{step-3-Don8gv0n.js → step-3-DAUCAcxW.js} +7 -7
  38. package/core/built/admin/assets/activitypub/{tabs-DTpfm-wu.js → tabs-B_6UuI7r.js} +3 -3
  39. package/core/built/admin/assets/activitypub/{topic-filter-Oee10885.js → topic-filter--FT1A_dc.js} +2 -2
  40. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
  41. package/core/built/admin/assets/admin-x-settings/{code-editor-view-CGZuiDGx.js → code-editor-view-Bp_sCJR0.js} +2 -2
  42. package/core/built/admin/assets/admin-x-settings/{index-CwEblQZC.js → index-BPXA3pTW.js} +5 -5
  43. package/core/built/admin/assets/admin-x-settings/{index-rr_UntGE.js → index-BS375mg1.js} +3 -3
  44. package/core/built/admin/assets/admin-x-settings/{index-BpM6W2br.js → index-B_PlQoEG.js} +3 -3
  45. package/core/built/admin/assets/admin-x-settings/{index-BGdS5xw4.js → index-BflhAbu2.js} +3 -3
  46. package/core/built/admin/assets/admin-x-settings/{index-DtWUeJhp.js → index-CftMe8fe.js} +3 -3
  47. package/core/built/admin/assets/admin-x-settings/{index-B65inCwj.js → index-DRB7FH6y.js} +2 -2
  48. package/core/built/admin/assets/admin-x-settings/{index-DSX261S9.js → index-DijZ3oAM.js} +2 -2
  49. package/core/built/admin/assets/admin-x-settings/{index-DF6YEs01.js → index-cV0Lof9H.js} +1526 -1500
  50. package/core/built/admin/assets/admin-x-settings/{index-CluZYkUC.js → index-qc4x3vZj.js} +3 -3
  51. package/core/built/admin/assets/admin-x-settings/{modals-Bf4KOU31.js → modals-CkjuEByw.js} +55 -67
  52. package/core/built/admin/assets/{arrow-right-BxP3aeA5.js → arrow-right-BF77Yx73.js} +1 -1
  53. package/core/built/admin/assets/{at-sign-Bf0y-jim.js → at-sign-DhJF3n_V.js} +1 -1
  54. package/core/built/admin/assets/{audience-DHR1WgFK.js → audience-DHPKKayV.js} +1 -1
  55. package/core/built/admin/assets/{automated-emails-DbJbDLib.js → automated-emails-DInKYTEC.js} +1 -1
  56. package/core/built/admin/assets/{automations-aLgTzSsZ.js → automations-DJpeZw9T.js} +1 -1
  57. package/core/built/admin/assets/automations-DpQKN9j0.js +1 -0
  58. package/core/built/admin/assets/{avatar-flipboard-B0F2LMW5.js → avatar-flipboard-yRdmFC6R.js} +1 -1
  59. package/core/built/admin/assets/{bluesky-sharing-DjhtIuyr.js → bluesky-sharing-qSiCERoR.js} +1 -1
  60. package/core/built/admin/assets/{chart-BGwxlD0z.js → chart-CP2N9ZC-.js} +1 -1
  61. package/core/built/admin/assets/{chevron-up-C2FeVzre.js → chevron-up-BQ6fOpFR.js} +1 -1
  62. package/core/built/admin/assets/{chunk.524.31b31f7958dfae110016.js → chunk.524.ad4560630d4d62fbceb5.js} +4 -4
  63. package/core/built/admin/assets/{chunk.582.a855020953886af6e83b.js → chunk.582.fd4878e4e0af31ef86c0.js} +6 -6
  64. package/core/built/admin/assets/{chunk.972.258e6b1dab79b2189f1c.js → chunk.98.69968e0f393d82c2949c.js} +4 -4
  65. package/core/built/admin/assets/{code-editor-view-DzPkm_nJ.js → code-editor-view-CVODiRVJ.js} +1 -1
  66. package/core/built/admin/assets/{comments-DgItXUWN.js → comments-CP-NxopD.js} +1 -1
  67. package/core/built/admin/assets/{content-helpers-BNefU_PE.js → content-helpers-Crzkusiq.js} +1 -1
  68. package/core/built/admin/assets/{copy-DoNNUZlb.js → copy-r1LFsU75.js} +1 -1
  69. package/core/built/admin/assets/{data-list-CO1cZQRU.js → data-list-CzSc7z-2.js} +1 -1
  70. package/core/built/admin/assets/{deleted-feed-item-DC2wSAhv.js → deleted-feed-item-PyEM8FiX.js} +1 -1
  71. package/core/built/admin/assets/{dropzone-BTODLafn.js → dropzone-DI8A9qO6.js} +1 -1
  72. package/core/built/admin/assets/{edit-profile-BMMpmWIZ.js → edit-profile-DlJ64lV7.js} +1 -1
  73. package/core/built/admin/assets/editor-CevNZicQ.js +8 -0
  74. package/core/built/admin/assets/{empty-indicator-Dza0FQlj.js → empty-indicator-Dxg05yWg.js} +1 -1
  75. package/core/built/admin/assets/{en-yf9qAqTu.js → en-D93GVp3o.js} +1 -1
  76. package/core/built/admin/assets/{eye-CAqnnfQF.js → eye-6POPPpTw.js} +1 -1
  77. package/core/built/admin/assets/{feed-BkNsbWiF.js → feed-P_RMYb5x.js} +1 -1
  78. package/core/built/admin/assets/{field-Ddb1ZOqM.js → field-DeNjwWrL.js} +1 -1
  79. package/core/built/admin/assets/{filter-query-core-5Mac92kh.js → filter-query-core-DFs5WSmA.js} +1 -1
  80. package/core/built/admin/assets/{filters-BCb4bgtg.js → filters-C_BSQgb7.js} +1 -1
  81. package/core/built/admin/assets/{gh-chart-C6zkIg8k.js → gh-chart-pNHAbeQn.js} +1 -1
  82. package/core/built/admin/assets/{ghost-5313258fd425ced5db6516acde01ba36.js → ghost-3f5b3569e0beec61266cb74077677fbc.js} +3 -3
  83. package/core/built/admin/assets/{growth-ezUlN8OE.js → growth-B83tVOYO.js} +1 -1
  84. package/core/built/admin/assets/{hash-DE2OmChI.js → hash-BCTATV5W.js} +1 -1
  85. package/core/built/admin/assets/{inbox-OthiHvlV.js → inbox-rE3c5suT.js} +1 -1
  86. package/core/built/admin/assets/{index-BLyzLfyx.js → index-7EiRc5Ay.js} +1 -1
  87. package/core/built/admin/assets/{index-CV5v0eFE.js → index-B5bpZGTI.js} +1 -1
  88. package/core/built/admin/assets/index-BNtzAd7H.css +1 -0
  89. package/core/built/admin/assets/{index-BC1yWeaJ.js → index-BT33w2Io.js} +1 -1
  90. package/core/built/admin/assets/{index-DjffQ3VJ.js → index-BbkXWdhb.js} +1 -1
  91. package/core/built/admin/assets/{index-DH6PA3W6.js → index-BfhSrnrJ.js} +1 -1
  92. package/core/built/admin/assets/{index-CRvK0Iac.js → index-BmR_OUpb.js} +1 -1
  93. package/core/built/admin/assets/{index-CNV7fxKZ.js → index-BuhMgn5c.js} +1 -1
  94. package/core/built/admin/assets/{index-pkixJbdq.js → index-C-075SzJ.js} +34 -34
  95. package/core/built/admin/assets/{index-DJoByVS8.js → index-CB3hJd9E.js} +1 -1
  96. package/core/built/admin/assets/{index-D3bb-MEJ.js → index-CCISLIQd.js} +1 -1
  97. package/core/built/admin/assets/{index-BIEhcKiV.js → index-Cnptpx11.js} +1 -1
  98. package/core/built/admin/assets/{index-DK1cu59e.js → index-CrBRU81r.js} +1 -1
  99. package/core/built/admin/assets/{index-B0_p1-R0.js → index-CscUHFJp.js} +1 -1
  100. package/core/built/admin/assets/{index-Bi5qzf86.js → index-D3ZzfuJt.js} +1 -1
  101. package/core/built/admin/assets/{index-CKPny_6u.js → index-D4wn-U0V.js} +1 -1
  102. package/core/built/admin/assets/{index-YzcsD17L.js → index-DNRlkUeu.js} +1 -1
  103. package/core/built/admin/assets/{index-BoMsDCxD.js → index-D_G6negZ.js} +1 -1
  104. package/core/built/admin/assets/{index-CwsmINtV.js → index-DfxHSK1s.js} +1 -1
  105. package/core/built/admin/assets/{index-X91I8PcS.js → index-Dpc3COTl.js} +1 -1
  106. package/core/built/admin/assets/{index-c1h-EQpd.js → index-UsbnU2jy.js} +1 -1
  107. package/core/built/admin/assets/{index-oKgNVmwC.js → index-VfrJzh5m.js} +1 -1
  108. package/core/built/admin/assets/{index-D5MrQBSe.js → index-fbxc8Dpt.js} +1 -1
  109. package/core/built/admin/assets/{input-group-BOLMrDfJ.js → input-group-BLygwiDK.js} +1 -1
  110. package/core/built/admin/assets/{koenig-lexical-DlzcWUPw.js → koenig-lexical-B9U3sBS5.js} +1 -1
  111. package/core/built/admin/assets/{kpi-card-BlF7JKRC.js → kpi-card-Bmnvvp4X.js} +1 -1
  112. package/core/built/admin/assets/{kpi-card-HDshm3lO.js → kpi-card-CGVtzdgJ.js} +1 -1
  113. package/core/built/admin/assets/{kpi-tabs-Dty6zMSP.js → kpi-tabs-DxbvMkIU.js} +1 -1
  114. package/core/built/admin/assets/{kpis-CFz4r5wW.js → kpis-CQBAGVjP.js} +1 -1
  115. package/core/built/admin/assets/{label-CVzNcbjb.js → label-DpTwHQi_.js} +1 -1
  116. package/core/built/admin/assets/{links-D9dHwDlP.js → links-Bdw25FJF.js} +1 -1
  117. package/core/built/admin/assets/{list-page-shoQED7N.js → list-page-BvTQwP5F.js} +1 -1
  118. package/core/built/admin/assets/{loader-circle-cad701gH.js → loader-circle-3HJkQKWf.js} +1 -1
  119. package/core/built/admin/assets/{mail-DG4PKpZy.js → mail-DvrqP73n.js} +1 -1
  120. package/core/built/admin/assets/{main-layout-BmFla3Se.js → main-layout-BAPpm0bz.js} +1 -1
  121. package/core/built/admin/assets/{members-d19Z6k1C.js → members-CF4CzJSf.js} +1 -1
  122. package/core/built/admin/assets/minus-DJIePlnU.js +1 -0
  123. package/core/built/admin/assets/modals-CVsVi0zu.js +35 -0
  124. package/core/built/admin/assets/{moderation-BWBLnh4h.js → moderation-BX1KH5QL.js} +1 -1
  125. package/core/built/admin/assets/{newsletter-DNY4MroI.js → newsletter-BlKeb58e.js} +1 -1
  126. package/core/built/admin/assets/{newsletters-B10iBPP-.js → newsletters-3In8H0Z5.js} +1 -1
  127. package/core/built/admin/assets/{note-S0b6wjRR.js → note-ybwJCAxm.js} +1 -1
  128. package/core/built/admin/assets/{offers-B-9mZqKd.js → offers-Cl0ZNEMr.js} +1 -1
  129. package/core/built/admin/assets/{onboarding-route-AMFn0X5G.js → onboarding-route-A3ECeMAi.js} +1 -1
  130. package/core/built/admin/assets/{overview-WKadyZRW.js → overview-DrMrfsk-.js} +1 -1
  131. package/core/built/admin/assets/{pagemenu-BofQcFaF.js → pagemenu-O7HbsehH.js} +1 -1
  132. package/core/built/admin/assets/{pencil-LfMcD0vS.js → pencil-CknTbJeE.js} +1 -1
  133. package/core/built/admin/assets/{pin-CayUjL8e.js → pin-BNs-JdQ3.js} +1 -1
  134. package/core/built/admin/assets/{post-analytics-DMHvt3Jl.js → post-analytics-Dk1VloZp.js} +1 -1
  135. package/core/built/admin/assets/{post-analytics-context-10cqioP1.js → post-analytics-context-kPL94Ivz.js} +1 -1
  136. package/core/built/admin/assets/{post-analytics-header-DlICE18K.js → post-analytics-header-CpMOozq-.js} +1 -1
  137. package/core/built/admin/assets/{post-share-modal-Cfwy2UwD.js → post-share-modal-F0dobdhF.js} +1 -1
  138. package/core/built/admin/assets/posts/{alert-dialog-cQK32v9G.js → alert-dialog-NoPLULhG.js} +5 -5
  139. package/core/built/admin/assets/posts/{app-utils-DQ2hixop.js → app-utils-BiM9QO4L.js} +3 -3
  140. package/core/built/admin/assets/posts/{automations-BqkU9-zY.js → automations-BgIAmq9f.js} +3 -3
  141. package/core/built/admin/assets/posts/{automations-CoilKpVH.js → automations-D_XwO405.js} +30 -30
  142. package/core/built/admin/assets/posts/{avatar-BejAO6Rj.js → avatar-BYsYOpmq.js} +5 -5
  143. package/core/built/admin/assets/posts/{badge-BYGRyd2f.js → badge-B54vLono.js} +2 -2
  144. package/core/built/admin/assets/posts/{button-CG2Dpjqy.js → button-DMLfwOQt.js} +3 -3
  145. package/core/built/admin/assets/posts/{check-fsC5BvGC.js → check-D6qDi62T.js} +2 -2
  146. package/core/built/admin/assets/posts/{comments-CsyF5C1S.js → comments-CyYCbvUG.js} +26 -26
  147. package/core/built/admin/assets/posts/{data-list-JslRF2F5.js → data-list-B0DrMm-x.js} +4 -4
  148. package/core/built/admin/assets/posts/{dialog-3KY3A8t5.js → dialog-DtEZpY1q.js} +3 -3
  149. package/core/built/admin/assets/posts/{editor-3HPW_xW4.js → editor-LJHLmUoW.js} +1943 -1943
  150. package/core/built/admin/assets/posts/{ellipsis-BhK5S70U.js → ellipsis-JeIF0Biz.js} +2 -2
  151. package/core/built/admin/assets/posts/{empty-indicator-C9DbE9wZ.js → empty-indicator-dktzN32y.js} +2 -2
  152. package/core/built/admin/assets/posts/{filters-BCBDCnQF.js → filters-D1L-LU_m.js} +10 -10
  153. package/core/built/admin/assets/posts/{get-site-timezone-83C41MGz.js → get-site-timezone-dsyl8nlB.js} +3 -3
  154. package/core/built/admin/assets/posts/{growth-COoL1kMe.js → growth-yX8UB_ce.js} +12 -12
  155. package/core/built/admin/assets/posts/{heading-D3VqNO3z.js → heading-CNte43wH.js} +2 -2
  156. package/core/built/admin/assets/posts/{hooks-5YpJYWOw.js → hooks-BIgv7QBp.js} +2 -2
  157. package/core/built/admin/assets/posts/{index-Dqn5WIFh.js → index-2FO6Vy0r.js} +11 -11
  158. package/core/built/admin/assets/posts/{input-C1qcZBGw.js → input-BycmlCp8.js} +3 -3
  159. package/core/built/admin/assets/posts/{input-surface-C4vUAdbV.js → input-surface-vY3ACaso.js} +2 -2
  160. package/core/built/admin/assets/posts/{koenig-lexical-NZjxFY0X.js → koenig-lexical-Cq8SUcZC.js} +2 -2
  161. package/core/built/admin/assets/posts/{kpis-C2FEXoUT.js → kpis-CAJcU1w0.js} +27 -27
  162. package/core/built/admin/assets/posts/{label-CmJzmJY7.js → label-BmLTflMF.js} +3 -3
  163. package/core/built/admin/assets/posts/{links-nzud7t_t.js → links-BVcmMZmF.js} +4 -4
  164. package/core/built/admin/assets/posts/{list-page-D_rv4TgN.js → list-page-DX2WRn5T.js} +4 -4
  165. package/core/built/admin/assets/posts/{loading-indicator-DAOD6O2b.js → loading-indicator-C6FD4nS6.js} +3 -3
  166. package/core/built/admin/assets/posts/{mail-CtH0gTpq.js → mail-BMyoNUVM.js} +2 -2
  167. package/core/built/admin/assets/posts/{main-layout-VqvCxOXQ.js → main-layout-ClnxdW1Y.js} +2 -2
  168. package/core/built/admin/assets/posts/{message-circle-D4bCjxKS.js → message-circle-DwfHX-p1.js} +2 -2
  169. package/core/built/admin/assets/posts/{newsletter-BfwFyv6d.js → newsletter-Bid5_Bim.js} +19 -19
  170. package/core/built/admin/assets/posts/{overview-DKGum6ro.js → overview-BXti6Wg_.js} +18 -18
  171. package/core/built/admin/assets/posts/{pencil-Cffx-vm3.js → pencil-ChTk8IpW.js} +2 -2
  172. package/core/built/admin/assets/posts/{plus-D2ekwsq0.js → plus-DIKls8C6.js} +2 -2
  173. package/core/built/admin/assets/posts/{popover-DdbdKflP.js → popover-DVZsAT5u.js} +4 -4
  174. package/core/built/admin/assets/posts/{post-analytics-BleJYoNZ.js → post-analytics-DBL_kS0g.js} +6 -6
  175. package/core/built/admin/assets/posts/{post-analytics-context-DT3Avn9Q.js → post-analytics-context-Dr5y5nxd.js} +6 -6
  176. package/core/built/admin/assets/posts/{post-analytics-header-BktlrM_v.js → post-analytics-header-LH2-Jzgp.js} +13 -13
  177. package/core/built/admin/assets/posts/{post-share-modal-G8v3ZuSg.js → post-share-modal-D4DrfNfJ.js} +6 -6
  178. package/core/built/admin/assets/posts/{posts-CdWV_nlm.js → posts-ByxhcCrn.js} +2 -2
  179. package/core/built/admin/assets/posts/posts.js +1 -1
  180. package/core/built/admin/assets/posts/{search-CXP00fG8.js → search-DZR23LP-.js} +2 -2
  181. package/core/built/admin/assets/posts/{settings-DEXxDYc2.js → settings-CWyOKKnK.js} +2 -2
  182. package/core/built/admin/assets/posts/{sheet-CXHmD_eT.js → sheet-CNe7II5c.js} +3 -3
  183. package/core/built/admin/assets/posts/{site-C6DajagV.js → site-H9RAiQVv.js} +2 -2
  184. package/core/built/admin/assets/posts/{skeleton-Bihnkn8q.js → skeleton-Cn6x1x_C.js} +3 -3
  185. package/core/built/admin/assets/posts/{source-icon-C3oQy3UI.js → source-icon-BZOYujd1.js} +4 -4
  186. package/core/built/admin/assets/posts/{stats-DFicPtCX.js → stats-ugHCg8Oe.js} +4 -4
  187. package/core/built/admin/assets/posts/{table-DSJmeU5t.js → table-dG3Q5X95.js} +2 -2
  188. package/core/built/admin/assets/posts/{tabs-oRdVpN8P.js → tabs--EW8uL5B.js} +4 -4
  189. package/core/built/admin/assets/posts/{tags-6v5Qt_05.js → tags-DVdOfiSf.js} +2 -2
  190. package/core/built/admin/assets/posts/{tags-Upnkm0CF.js → tags-i0HJqNYU.js} +16 -16
  191. package/core/built/admin/assets/posts/{tooltip-Ci7pesWi.js → tooltip-ZvsT1Fs9.js} +4 -4
  192. package/core/built/admin/assets/posts/{trash-2-NCYWjRlg.js → trash-2-BT9fEHUL.js} +2 -2
  193. package/core/built/admin/assets/posts/{value-CcwzOh2_.js → value-DoFcsWDk.js} +2 -2
  194. package/core/built/admin/assets/posts/{virtual-list-window-CNahQBXY.js → virtual-list-window-Bt47XODn.js} +3 -3
  195. package/core/built/admin/assets/posts/{web-C3KrPq0T.js → web-BMTRLT-h.js} +20 -20
  196. package/core/built/admin/assets/posts/{zap-k30mZEuO.js → zap-DcIfEzKX.js} +2 -2
  197. package/core/built/admin/assets/{posts-DAfLuxAA.js → posts-DODOCB1E.js} +1 -1
  198. package/core/built/admin/assets/{power-Drkzg1xX.js → power-BZ7BM82m.js} +1 -1
  199. package/core/built/admin/assets/{referrers-TYxTJ2On.js → referrers-xwNb6L-8.js} +1 -1
  200. package/core/built/admin/assets/{repeat-CTM1qAzl.js → repeat-DHMCQMCA.js} +1 -1
  201. package/core/built/admin/assets/{reply-DXN16g7W.js → reply-DoLURZi2.js} +1 -1
  202. package/core/built/admin/assets/{rocket-DLixlWs0.js → rocket-BDH89dZr.js} +1 -1
  203. package/core/built/admin/assets/{select-DbiLEqYb.js → select-oE39QRll.js} +1 -1
  204. package/core/built/admin/assets/{send-B71C_46D.js → send-QgRN_yRy.js} +1 -1
  205. package/core/built/admin/assets/{settings-BBMPA3MP.js → settings-CF4NLxuS.js} +4 -4
  206. package/core/built/admin/assets/{settings-lSFHFrsJ.js → settings-D7e6KLAi.js} +1 -1
  207. package/core/built/admin/assets/{share-modal-CXrYja6o.js → share-modal-BxIh7i7o.js} +1 -1
  208. package/core/built/admin/assets/{sort-button-WYo-9Kge.js → sort-button-CEe_40Pi.js} +1 -1
  209. package/core/built/admin/assets/{source-icon-D6BzjYPP.js → source-icon-CzTNLO6j.js} +1 -1
  210. package/core/built/admin/assets/{sprout-DVfG_qhy.js → sprout-B2fN41p-.js} +1 -1
  211. package/core/built/admin/assets/{square-BbUJwQJi.js → square-CJZsjE-3.js} +1 -1
  212. package/core/built/admin/assets/{stats-CnXALQ4j.js → stats-jTxCK1CB.js} +1 -1
  213. package/core/built/admin/assets/{stats-view-B-yeMUkl.js → stats-view-B4jz69N8.js} +1 -1
  214. package/core/built/admin/assets/{step-1-DUl_hlpV.js → step-1-BovjhDNc.js} +1 -1
  215. package/core/built/admin/assets/{step-2-DjX_A4Ke.js → step-2-ClG40BfX.js} +1 -1
  216. package/core/built/admin/assets/{step-3-CnDCCA8T.js → step-3-ZMSyEHZI.js} +1 -1
  217. package/core/built/admin/assets/{table-AagTvMa9.js → table-B8Pj96or.js} +1 -1
  218. package/core/built/admin/assets/{tabs-IDJWnQD-.js → tabs-BZosOk0o.js} +1 -1
  219. package/core/built/admin/assets/{tags-CXAcIDw-.js → tags-BpWoYBfB.js} +1 -1
  220. package/core/built/admin/assets/{tags-C3lykbgu.js → tags-DZukMyuU.js} +1 -1
  221. package/core/built/admin/assets/{textarea-Cc3nUXep.js → textarea-BtnzsOk7.js} +1 -1
  222. package/core/built/admin/assets/{thumbs-down-SsjC2lMD.js → thumbs-down-CZTzyz7D.js} +1 -1
  223. package/core/built/admin/assets/{tiers-J8AeZmRd.js → tiers-Do7zBqAk.js} +1 -1
  224. package/core/built/admin/assets/{toggle-group-BzD8yimX.js → toggle-group-abk9FYXi.js} +1 -1
  225. package/core/built/admin/assets/{topic-filter-CA_L6ehz.js → topic-filter-CdMvjkmc.js} +1 -1
  226. package/core/built/admin/assets/{trash-BKhRqxsX.js → trash-DNXRsBf0.js} +1 -1
  227. package/core/built/admin/assets/{underline-_HIgX6e1.js → underline-HXWuVCAi.js} +1 -1
  228. package/core/built/admin/assets/{undo-2-ClkkOS0O.js → undo-2-C2ntNJKC.js} +1 -1
  229. package/core/built/admin/assets/{upload-DEPHbyvo.js → upload-D52l7HIb.js} +1 -1
  230. package/core/built/admin/assets/{use-growth-stats-BM2qWt0C.js → use-growth-stats-Dgs0UxDT.js} +1 -1
  231. package/core/built/admin/assets/{use-simple-pagination-zyklfekn.js → use-simple-pagination-Cj2rVggA.js} +1 -1
  232. package/core/built/admin/assets/{user-round-check-DoaGU2hC.js → user-round-check-C0GdT99c.js} +1 -1
  233. package/core/built/admin/assets/{user-round-x-CwEKNuU6.js → user-round-x-CVCvxuUG.js} +1 -1
  234. package/core/built/admin/assets/{virtual-list-window-C0Cv0gUC.js → virtual-list-window-mdQS2bt_.js} +1 -1
  235. package/core/built/admin/assets/{wallet-cards-BjdG5glh.js → wallet-cards-CtLF01jQ.js} +1 -1
  236. package/core/built/admin/assets/{web-BZNnanCT.js → web-DTib8_n8.js} +1 -1
  237. package/core/built/admin/index.html +6 -6
  238. package/core/frontend/helpers/ghost_foot.js +12 -2
  239. package/core/frontend/helpers/prev_post.js +1 -1
  240. package/core/frontend/helpers/tpl/gift-toast.hbs +130 -0
  241. package/core/frontend/services/proxy.js +9 -0
  242. package/core/frontend/services/routing/controllers/entry/canonical-url.js +15 -0
  243. package/core/frontend/services/routing/controllers/entry/canonical-url.ts +14 -0
  244. package/core/frontend/services/routing/controllers/entry/gift-links.js +82 -0
  245. package/core/frontend/services/routing/controllers/entry/gift-links.ts +92 -0
  246. package/core/frontend/services/routing/controllers/entry/markdown.js +62 -0
  247. package/core/frontend/services/routing/controllers/entry/markdown.ts +66 -0
  248. package/core/frontend/services/routing/controllers/entry.js +96 -105
  249. package/core/frontend/services/routing/controllers/entry.ts +97 -0
  250. package/core/frontend/services/routing/controllers/index.js +1 -1
  251. package/core/frontend/web/middleware/frontend-caching.js +9 -0
  252. package/core/server/adapters/cache/Redis.js +0 -1
  253. package/core/server/adapters/redirects/RedirectsStoreBase.d.ts +0 -4
  254. package/core/server/adapters/scheduling/scheduling-default.js +8 -1
  255. package/core/server/api/endpoints/gift-links.js +23 -16
  256. package/core/server/api/endpoints/gift-links.ts +25 -17
  257. package/core/server/api/endpoints/index.js +0 -1
  258. package/core/server/api/endpoints/previews.js +3 -24
  259. package/core/server/api/endpoints/utils/serializers/output/gift-links.js +4 -4
  260. package/core/server/api/endpoints/utils/serializers/output/gift-links.ts +4 -4
  261. package/core/server/api/endpoints/utils/serializers/output/index.js +0 -1
  262. package/core/server/api/endpoints/utils/validators/input/automation_email_previews.js +8 -9
  263. package/core/server/api/endpoints/utils/validators/input/index.js +0 -1
  264. package/core/server/data/migrations/versions/6.48/2026-06-23-00-00-00-drop-gift-links-revoked-at.js +6 -0
  265. package/core/server/data/migrations/versions/6.48/2026-06-23-00-00-01-rename-gift-links-revoke-all-permission-to-remove-all.js +37 -0
  266. package/core/server/data/migrations/versions/6.48/2026-06-24-00-00-00-drop-gift-links-redemption-columns.js +10 -0
  267. package/core/server/data/schema/fixtures/fixtures.json +2 -2
  268. package/core/server/data/schema/schema.js +1 -4
  269. package/core/server/data/seeders/data-generator.js +4 -1
  270. package/core/server/data/seeders/importers/members-login-events-importer.js +0 -1
  271. package/core/server/data/seeders/importers/members-newsletters-importer.js +0 -1
  272. package/core/server/data/seeders/importers/members-paid-subscription-events-importer.js +0 -1
  273. package/core/server/data/seeders/importers/members-status-events-importer.js +0 -1
  274. package/core/server/data/seeders/importers/members-stripe-customers-importer.js +0 -1
  275. package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +0 -1
  276. package/core/server/data/seeders/importers/members-subscribe-events-importer.js +0 -1
  277. package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +0 -1
  278. package/core/server/lib/lexical.js +62 -36
  279. package/core/server/lib/post-revisions.js +0 -2
  280. package/core/server/lib/post-revisions.ts +0 -3
  281. package/core/server/models/index.js +0 -3
  282. package/core/server/services/auth/reset-authentication.js +0 -2
  283. package/core/server/services/auth/reset-authentication.ts +0 -3
  284. package/core/server/services/auth/session/index.js +0 -1
  285. package/core/server/services/automations/automations-api.js +59 -17
  286. package/core/server/services/automations/automations-api.ts +69 -16
  287. package/core/server/services/automations/database-automations-repository.js +58 -3
  288. package/core/server/services/automations/database-automations-repository.ts +70 -3
  289. package/core/server/services/automations/stale-lock-cutoff.js +7 -0
  290. package/core/server/services/automations/stale-lock-cutoff.ts +5 -0
  291. package/core/server/services/email-address/email-address-parser.js.d.ts +0 -1
  292. package/core/server/services/email-service/batch-sending-service.js +99 -26
  293. package/core/server/services/email-service/email-service-wrapper.js +7 -2
  294. package/core/server/services/email-service/email-service.js +146 -4
  295. package/core/server/services/gift-links/database.js +0 -3
  296. package/core/server/services/gift-links/database.ts +1 -4
  297. package/core/server/services/gift-links/index.js +2 -2
  298. package/core/server/services/gift-links/index.ts +4 -2
  299. package/core/server/services/gift-links/models.js +0 -2
  300. package/core/server/services/gift-links/models.ts +0 -2
  301. package/core/server/services/gift-links/queries.js +2 -2
  302. package/core/server/services/gift-links/queries.ts +2 -2
  303. package/core/server/services/gift-links/serializers.js +0 -2
  304. package/core/server/services/gift-links/serializers.ts +0 -2
  305. package/core/server/services/gift-links/service.js +54 -35
  306. package/core/server/services/gift-links/service.ts +70 -35
  307. package/core/server/services/gifts/gift-reminder-scheduler.js +0 -2
  308. package/core/server/services/gifts/gift-reminder-scheduler.ts +0 -2
  309. package/core/server/services/indexnow.js +8 -1
  310. package/core/server/services/internal-keys/index.js +0 -1
  311. package/core/server/services/internal-keys/index.ts +0 -2
  312. package/core/server/services/lib/dynamic-redirect-manager.d.ts +0 -1
  313. package/core/server/services/lib/in-memory-repository.js +0 -1
  314. package/core/server/services/lib/in-memory-repository.ts +0 -2
  315. package/core/server/services/members/api.js +0 -3
  316. package/core/server/services/members/create-paid-member-shim.js +31 -0
  317. package/core/server/services/members/create-paid-member-shim.ts +31 -0
  318. package/core/server/services/members/emails/signin.js +0 -1
  319. package/core/server/services/members/members-api/members-api.js +0 -2
  320. package/core/server/services/members/members-api/repositories/member-repository.js +0 -3
  321. package/core/server/services/members/service.js +4 -0
  322. package/core/server/services/post-scheduling/index.js +0 -2
  323. package/core/server/services/post-scheduling/index.ts +0 -2
  324. package/core/server/services/post-scheduling/post-scheduling.js +0 -5
  325. package/core/server/services/post-scheduling/post-scheduling.ts +0 -5
  326. package/core/server/services/recommendations/service/bookshelf-repository.js +0 -1
  327. package/core/server/services/recommendations/service/bookshelf-repository.ts +0 -4
  328. package/core/server/services/recommendations/service/incoming-recommendation-service.js +0 -1
  329. package/core/server/services/recommendations/service/incoming-recommendation-service.ts +0 -1
  330. package/core/server/services/recommendations/service/recommendation-controller.js +0 -1
  331. package/core/server/services/recommendations/service/recommendation-controller.ts +0 -1
  332. package/core/server/services/recommendations/service/recommendation-metadata-service.js +0 -1
  333. package/core/server/services/recommendations/service/recommendation-metadata-service.ts +0 -1
  334. package/core/server/services/recommendations/service/recommendation.js +0 -1
  335. package/core/server/services/recommendations/service/recommendation.ts +0 -1
  336. package/core/server/services/remote-flags/index.js +0 -1
  337. package/core/server/services/remote-flags/index.ts +0 -1
  338. package/core/server/services/slack.js +5 -1
  339. package/core/server/services/update-check/index.js +0 -1
  340. package/core/server/services/url/lazy-find-resource.js +0 -1
  341. package/core/server/services/url/lazy-find-resource.ts +0 -2
  342. package/core/server/services/url/lazy-url-service.js +0 -1
  343. package/core/server/services/url/lazy-url-service.ts +0 -2
  344. package/core/server/services/url/permalink-matcher.js +0 -1
  345. package/core/server/services/url/permalink-matcher.ts +0 -2
  346. package/core/server/services/url/router-filter.js +0 -2
  347. package/core/server/services/url/router-filter.ts +0 -2
  348. package/core/server/services/url/url-service-facade.js +0 -1
  349. package/core/server/services/url/url-service-facade.ts +0 -2
  350. package/core/server/services/verification/verification-webhook-service.js +0 -1
  351. package/core/server/services/verification/verification-webhook-service.ts +0 -1
  352. package/core/server/web/api/endpoints/admin/routes.js +7 -7
  353. package/core/shared/events-ts/post-deleted-event.js +0 -2
  354. package/core/shared/events-ts/post-deleted-event.ts +0 -2
  355. package/core/shared/labs.js +0 -1
  356. package/package.json +8 -16
  357. package/pnpm-lock.yaml +162 -113
  358. package/core/built/admin/assets/automations-BthXBzzq.js +0 -1
  359. package/core/built/admin/assets/editor-91xrI2A3.js +0 -8
  360. package/core/built/admin/assets/index-BYb09f-7.css +0 -1
  361. package/core/built/admin/assets/minus-DmrVsJ-z.js +0 -1
  362. package/core/built/admin/assets/modals-dnqxCS3l.js +0 -35
  363. package/core/server/models/outbox.js +0 -24
  364. package/core/server/services/outbox/events/start-outbox-processing-event.js +0 -22
  365. package/core/server/services/outbox/handlers/member-created.js +0 -85
  366. package/core/server/services/outbox/index.js +0 -49
  367. package/core/server/services/outbox/jobs/index.js +0 -30
  368. package/core/server/services/outbox/jobs/lib/constants.js +0 -17
  369. package/core/server/services/outbox/jobs/lib/process-entries.js +0 -117
  370. package/core/server/services/outbox/jobs/lib/process-outbox.js +0 -96
  371. package/core/server/services/outbox/jobs/outbox-job.js +0 -36
  372. package/types/ghost-storage-base.d.ts +0 -22
@@ -31,7 +31,6 @@ type PostRevisionsDeps = {
31
31
  max_revisions: number;
32
32
  revision_interval_ms: number;
33
33
  },
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
34
  model: any
36
35
  }
37
36
 
@@ -44,7 +43,6 @@ type RevisionResult = {
44
43
 
45
44
  export class PostRevisions {
46
45
  config: PostRevisionsDeps['config'];
47
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
46
  model: any;
49
47
 
50
48
  constructor(deps: PostRevisionsDeps) {
@@ -128,7 +126,6 @@ export class PostRevisions {
128
126
  };
129
127
  }
130
128
 
131
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
129
  async removeAuthorFromRevisions(authorId: string, options: any): Promise<void> {
133
130
  const revisions = await this.model.findAll({
134
131
  filter: `author_id:'${authorId}'`,
@@ -1,4 +1,3 @@
1
- /* eslint-disable max-lines */
2
1
 
3
2
  /**
4
3
  * Dependencies
@@ -49,7 +48,6 @@ const {MobiledocRevision} = require('./mobiledoc-revision');
49
48
  const {Newsletter} = require('./newsletter');
50
49
  const {OfferRedemption} = require('./offer-redemption');
51
50
  const {Offer} = require('./offer');
52
- const {Outbox} = require('./outbox');
53
51
  const {Permission, Permissions} = require('./permission');
54
52
  const {PostRevision} = require('./post-revision');
55
53
  const {Post, Posts} = require('./post');
@@ -147,7 +145,6 @@ exports.MobiledocRevision = MobiledocRevision;
147
145
  exports.Newsletter = Newsletter;
148
146
  exports.OfferRedemption = OfferRedemption;
149
147
  exports.Offer = Offer;
150
- exports.Outbox = Outbox;
151
148
  exports.Permission = Permission;
152
149
  exports.Permissions = Permissions;
153
150
  exports.PostRevision = PostRevision;
@@ -5,9 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.default = resetAuthentication;
7
7
  const internal_keys_1 = __importDefault(require("../internal-keys"));
8
- // eslint-disable-next-line @typescript-eslint/no-require-imports
9
8
  const modelsDefault = require('../../models');
10
- // eslint-disable-next-line @typescript-eslint/no-require-imports
11
9
  const { deleteAllSessions: deleteAllSessionsDefault } = require('./session');
12
10
  /**
13
11
  * Rotation, user lock and the audit row commit in a single transaction so
@@ -1,10 +1,7 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import type {Knex} from 'knex';
3
2
  import type {InternalApiKey, InternalKeys} from '../internal-keys';
4
3
  import internalKeysDefault from '../internal-keys';
5
- // eslint-disable-next-line @typescript-eslint/no-require-imports
6
4
  const modelsDefault = require('../../models');
7
- // eslint-disable-next-line @typescript-eslint/no-require-imports
8
5
  const {deleteAllSessions: deleteAllSessionsDefault} = require('./session');
9
6
 
10
7
  interface ResetAuthenticationArgs {
@@ -15,7 +15,6 @@ const {blogIcon} = require('../../../lib/image');
15
15
  const url = require('url');
16
16
 
17
17
  // TODO: We have too many lines here, should move functions out into a utils module
18
- /* eslint-disable max-lines */
19
18
 
20
19
  function getOriginOfRequest(req) {
21
20
  const getHeader = (name) => {
@@ -12,7 +12,6 @@ exports.fetchAndLockSteps = fetchAndLockSteps;
12
12
  exports.finishStepAndEnqueueNext = finishStepAndEnqueueNext;
13
13
  exports.markStepTerminal = markStepTerminal;
14
14
  exports.retryStep = retryStep;
15
- /* eslint-disable @typescript-eslint/no-require-imports */
16
15
  const errors_1 = __importDefault(require("@tryghost/errors"));
17
16
  const tpl_1 = __importDefault(require("@tryghost/tpl"));
18
17
  const bson_objectid_1 = __importDefault(require("bson-objectid"));
@@ -23,6 +22,7 @@ const { knex } = require('../../data/db');
23
22
  const domainEvents = require('@tryghost/domain-events');
24
23
  const labs = require('../../../shared/labs');
25
24
  const config = require('../../../shared/config');
25
+ const lexicalLib = require('../../lib/lexical');
26
26
  const StartAutomationsPollEvent = require('./events/start-automations-poll-event');
27
27
  const MAX_AUTOMATION_ACTIONS = 20;
28
28
  const messages = {
@@ -35,7 +35,8 @@ const messages = {
35
35
  invalidAutomationEdge: 'Automation edges cannot connect an action to itself.',
36
36
  invalidAutomationGraphShape: 'Automation graph must be a single linear path without branches or cycles.',
37
37
  emptyEmailSubjectWhenActive: 'Active automations require a subject line for every email.',
38
- emptyEmailBodyWhenActive: 'Active automations require a body for every email.'
38
+ emptyEmailBodyWhenActive: 'Active automations require a body for every email.',
39
+ invalidEmailLexical: 'Email lexical must be a well-formed Lexical document.'
39
40
  };
40
41
  const objectIdSchema = zod_1.z.string().refine(value => bson_objectid_1.default.isValid(value));
41
42
  const waitActionSchema = zod_1.z.object({
@@ -50,15 +51,7 @@ const sendEmailActionSchema = zod_1.z.object({
50
51
  type: zod_1.z.literal('send_email'),
51
52
  data: zod_1.z.object({
52
53
  email_subject: zod_1.z.string(),
53
- email_lexical: zod_1.z.string().refine((value) => {
54
- try {
55
- JSON.parse(value);
56
- return true;
57
- }
58
- catch {
59
- return false;
60
- }
61
- }),
54
+ email_lexical: zod_1.z.string(),
62
55
  email_design_setting_id: zod_1.z.string().min(1)
63
56
  }).strict()
64
57
  }).strict();
@@ -91,7 +84,7 @@ async function read(automationId) {
91
84
  return automation;
92
85
  }
93
86
  async function edit(automationId, data) {
94
- const parsedData = validateEditData(data);
87
+ const parsedData = await validateEditData(data);
95
88
  const automation = await repository.edit(automationId, parsedData);
96
89
  if (!automation) {
97
90
  throw new errors_1.default.NotFoundError({
@@ -100,7 +93,7 @@ async function edit(automationId, data) {
100
93
  }
101
94
  return automation;
102
95
  }
103
- function validateEditData(data) {
96
+ async function validateEditData(data) {
104
97
  const result = editAutomationDataSchema.safeParse(data);
105
98
  if (!result.success) {
106
99
  if (result.error.issues.some(issue => issue.path[0] === 'status')) {
@@ -109,9 +102,29 @@ function validateEditData(data) {
109
102
  throwValidationError(buildInvalidAutomationPayloadMessage(result.error.issues));
110
103
  }
111
104
  validateGraph(result.data.actions, result.data.edges);
105
+ await validateEmailLexical(result.data.actions);
112
106
  validateActiveEmailSteps(result.data.status, result.data.actions);
113
107
  return result.data;
114
108
  }
109
+ async function validateEmailLexical(actions) {
110
+ await Promise.all(actions.map(async (action) => {
111
+ if (action.type !== 'send_email') {
112
+ return;
113
+ }
114
+ const lexical = action.data.email_lexical;
115
+ // Empty editor documents are valid draft state and are classified by
116
+ // active-body validation below. Invalid JSON is not skipped here.
117
+ if (isValidEmptyLexical(lexical)) {
118
+ return;
119
+ }
120
+ if (isMalformedEmptyLexical(lexical)) {
121
+ throwValidationError(messages.invalidEmailLexical, 'actions');
122
+ }
123
+ if (!await lexicalLib.validate(lexical)) {
124
+ throwValidationError(messages.invalidEmailLexical, 'actions');
125
+ }
126
+ }));
127
+ }
115
128
  // Drafts may persist empty email steps, but an active automation must have a
116
129
  // complete subject and body for every email it sends — mirroring the editor's
117
130
  // publish-time validation.
@@ -134,15 +147,44 @@ function validateActiveEmailSteps(status, actions) {
134
147
  function isEmptyLexical(lexical) {
135
148
  try {
136
149
  const parsed = JSON.parse(lexical);
137
- const children = parsed?.root?.children;
138
- if (!children || children.length === 0) {
139
- return true;
150
+ return isEmptyParsedLexical(parsed);
151
+ }
152
+ catch {
153
+ return true;
154
+ }
155
+ }
156
+ function isValidEmptyLexical(lexical) {
157
+ try {
158
+ return isEmptyParsedLexical(JSON.parse(lexical));
159
+ }
160
+ catch {
161
+ return false;
162
+ }
163
+ }
164
+ function isMalformedEmptyLexical(lexical) {
165
+ try {
166
+ const children = JSON.parse(lexical)?.root?.children;
167
+ if (!Array.isArray(children) || children.length !== 1 || children[0].type !== 'paragraph') {
168
+ return false;
140
169
  }
141
- return children.length === 1 && children[0].type === 'paragraph' && (!children[0].children || children[0].children.length === 0);
170
+ return !Array.isArray(children[0].children);
142
171
  }
143
172
  catch {
173
+ return false;
174
+ }
175
+ }
176
+ function isEmptyParsedLexical(parsed) {
177
+ const children = parsed?.root?.children;
178
+ if (!Array.isArray(children)) {
179
+ return false;
180
+ }
181
+ if (children.length === 0) {
144
182
  return true;
145
183
  }
184
+ if (children.length !== 1 || children[0].type !== 'paragraph') {
185
+ return false;
186
+ }
187
+ return Array.isArray(children[0].children) && children[0].children.length === 0;
146
188
  }
147
189
  function buildInvalidAutomationPayloadMessage(issues) {
148
190
  if (!issues.length) {
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-require-imports */
2
1
  import errors from '@tryghost/errors';
3
2
  import tpl from '@tryghost/tpl';
4
3
  import ObjectId from 'bson-objectid';
@@ -14,6 +13,7 @@ const {knex} = require('../../data/db');
14
13
  const domainEvents = require('@tryghost/domain-events');
15
14
  const labs = require('../../../shared/labs');
16
15
  const config = require('../../../shared/config');
16
+ const lexicalLib = require('../../lib/lexical');
17
17
  const StartAutomationsPollEvent = require('./events/start-automations-poll-event');
18
18
 
19
19
  const MAX_AUTOMATION_ACTIONS = 20;
@@ -28,7 +28,8 @@ const messages = {
28
28
  invalidAutomationEdge: 'Automation edges cannot connect an action to itself.',
29
29
  invalidAutomationGraphShape: 'Automation graph must be a single linear path without branches or cycles.',
30
30
  emptyEmailSubjectWhenActive: 'Active automations require a subject line for every email.',
31
- emptyEmailBodyWhenActive: 'Active automations require a body for every email.'
31
+ emptyEmailBodyWhenActive: 'Active automations require a body for every email.',
32
+ invalidEmailLexical: 'Email lexical must be a well-formed Lexical document.'
32
33
  };
33
34
 
34
35
  const objectIdSchema = z.string().refine(value => ObjectId.isValid(value));
@@ -46,14 +47,7 @@ const sendEmailActionSchema = z.object({
46
47
  type: z.literal('send_email'),
47
48
  data: z.object({
48
49
  email_subject: z.string(),
49
- email_lexical: z.string().refine((value) => {
50
- try {
51
- JSON.parse(value);
52
- return true;
53
- } catch {
54
- return false;
55
- }
56
- }),
50
+ email_lexical: z.string(),
57
51
  email_design_setting_id: z.string().min(1)
58
52
  }).strict()
59
53
  }).strict();
@@ -94,7 +88,7 @@ export async function read(automationId: string) {
94
88
  }
95
89
 
96
90
  export async function edit(automationId: string, data: unknown) {
97
- const parsedData = validateEditData(data);
91
+ const parsedData = await validateEditData(data);
98
92
 
99
93
  const automation = await repository.edit(automationId, parsedData);
100
94
 
@@ -107,7 +101,7 @@ export async function edit(automationId: string, data: unknown) {
107
101
  return automation;
108
102
  }
109
103
 
110
- function validateEditData(data: unknown): EditAutomationData {
104
+ async function validateEditData(data: unknown): Promise<EditAutomationData> {
111
105
  const result = editAutomationDataSchema.safeParse(data);
112
106
 
113
107
  if (!result.success) {
@@ -119,10 +113,35 @@ function validateEditData(data: unknown): EditAutomationData {
119
113
  }
120
114
 
121
115
  validateGraph(result.data.actions, result.data.edges);
116
+ await validateEmailLexical(result.data.actions);
122
117
  validateActiveEmailSteps(result.data.status, result.data.actions);
123
118
  return result.data;
124
119
  }
125
120
 
121
+ async function validateEmailLexical(actions: EditAutomationData['actions']) {
122
+ await Promise.all(actions.map(async (action) => {
123
+ if (action.type !== 'send_email') {
124
+ return;
125
+ }
126
+
127
+ const lexical = action.data.email_lexical;
128
+
129
+ // Empty editor documents are valid draft state and are classified by
130
+ // active-body validation below. Invalid JSON is not skipped here.
131
+ if (isValidEmptyLexical(lexical)) {
132
+ return;
133
+ }
134
+
135
+ if (isMalformedEmptyLexical(lexical)) {
136
+ throwValidationError(messages.invalidEmailLexical, 'actions');
137
+ }
138
+
139
+ if (!await lexicalLib.validate(lexical)) {
140
+ throwValidationError(messages.invalidEmailLexical, 'actions');
141
+ }
142
+ }));
143
+ }
144
+
126
145
  // Drafts may persist empty email steps, but an active automation must have a
127
146
  // complete subject and body for every email it sends — mirroring the editor's
128
147
  // publish-time validation.
@@ -149,16 +168,50 @@ function validateActiveEmailSteps(status: EditAutomationData['status'], actions:
149
168
  function isEmptyLexical(lexical: string): boolean {
150
169
  try {
151
170
  const parsed = JSON.parse(lexical);
152
- const children = parsed?.root?.children;
171
+ return isEmptyParsedLexical(parsed);
172
+ } catch {
173
+ return true;
174
+ }
175
+ }
153
176
 
154
- if (!children || children.length === 0) {
155
- return true;
177
+ function isValidEmptyLexical(lexical: string): boolean {
178
+ try {
179
+ return isEmptyParsedLexical(JSON.parse(lexical));
180
+ } catch {
181
+ return false;
182
+ }
183
+ }
184
+
185
+ function isMalformedEmptyLexical(lexical: string): boolean {
186
+ try {
187
+ const children = JSON.parse(lexical)?.root?.children;
188
+
189
+ if (!Array.isArray(children) || children.length !== 1 || children[0].type !== 'paragraph') {
190
+ return false;
156
191
  }
157
192
 
158
- return children.length === 1 && children[0].type === 'paragraph' && (!children[0].children || children[0].children.length === 0);
193
+ return !Array.isArray(children[0].children);
159
194
  } catch {
195
+ return false;
196
+ }
197
+ }
198
+
199
+ function isEmptyParsedLexical(parsed: {root?: {children?: Array<{type?: string; children?: unknown}>}}): boolean {
200
+ const children = parsed?.root?.children;
201
+
202
+ if (!Array.isArray(children)) {
203
+ return false;
204
+ }
205
+
206
+ if (children.length === 0) {
160
207
  return true;
161
208
  }
209
+
210
+ if (children.length !== 1 || children[0].type !== 'paragraph') {
211
+ return false;
212
+ }
213
+
214
+ return Array.isArray(children[0].children) && children[0].children.length === 0;
162
215
  }
163
216
 
164
217
  function buildInvalidAutomationPayloadMessage(issues: z.core.$ZodIssue[]) {
@@ -11,7 +11,7 @@ const bson_objectid_1 = __importDefault(require("bson-objectid"));
11
11
  const dequal_1 = require("dequal");
12
12
  const moment_1 = __importDefault(require("moment"));
13
13
  const constants_1 = require("../member-welcome-emails/constants");
14
- const constants_2 = require("./constants");
14
+ const stale_lock_cutoff_1 = require("./stale-lock-cutoff");
15
15
  const HOUR_MS = 60 * 60 * 1000;
16
16
  const DATABASE_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
17
17
  const DEFAULT_WELCOME_EMAIL_AUTOMATIONS = [{
@@ -60,12 +60,16 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
60
60
  if (!automation) {
61
61
  return null;
62
62
  }
63
+ const now = new Date();
63
64
  const updatedAutomation = await updateAutomation(trx, {
64
65
  ...automation,
65
66
  status: data.status,
66
- updated_at: toDatabaseDate(new Date())
67
+ updated_at: toDatabaseDate(now)
67
68
  });
68
69
  await replaceAutomationGraph(trx, updatedAutomation.id, data.actions, data.edges);
70
+ if (updatedAutomation.status === 'inactive') {
71
+ await cancelCancelablePendingStepsForAutomation(trx, updatedAutomation.id, now);
72
+ }
69
73
  return await buildAutomation(trx, updatedAutomation);
70
74
  });
71
75
  },
@@ -210,7 +214,7 @@ async function fetchAndLockSteps(trx, limit) {
210
214
  // 3. Select any rows we successfully locked.
211
215
  const now = new Date();
212
216
  const nowString = toDatabaseDate(now);
213
- const staleLockCutoff = new Date(now.getTime() - constants_2.LOCK_TIMEOUT_MS);
217
+ const staleLockCutoff = (0, stale_lock_cutoff_1.getStaleLockCutoff)(now);
214
218
  const staleLockCutoffString = toDatabaseDate(staleLockCutoff);
215
219
  const lockId = node_crypto_1.default.randomUUID();
216
220
  // 1. Select up to `limit` candidate rows.
@@ -349,6 +353,9 @@ async function finishStepAndEnqueueNext(trx, options) {
349
353
  if (!didFinish) {
350
354
  return null;
351
355
  }
356
+ if (!await isRunAutomationActive(trx, step.automation_run_id)) {
357
+ return null;
358
+ }
352
359
  const next = await findNextActionRevision(trx, step.action_id);
353
360
  if (!next) {
354
361
  return null;
@@ -387,6 +394,10 @@ async function markStepTerminal(trx, step, status) {
387
394
  });
388
395
  }
389
396
  async function retryStep(trx, step, retryAt) {
397
+ if (!await isStepRunAutomationActive(trx, step.id)) {
398
+ await markStepTerminal(trx, step, 'automation disabled');
399
+ return false;
400
+ }
390
401
  const nowString = toDatabaseDate(new Date());
391
402
  return await updateStep(trx, step, {
392
403
  status: 'pending',
@@ -416,6 +427,50 @@ function getReadyAtForAction(action, now, fakeWaitHoursMultiplier) {
416
427
  }
417
428
  }
418
429
  }
430
+ async function cancelCancelablePendingStepsForAutomation(trx, automationId, now) {
431
+ const nowString = toDatabaseDate(now);
432
+ const staleLockCutoff = toDatabaseDate((0, stale_lock_cutoff_1.getStaleLockCutoff)(now));
433
+ await trx('automation_run_steps')
434
+ .update({
435
+ status: 'automation disabled',
436
+ finished_at: nowString,
437
+ updated_at: nowString,
438
+ locked_by: null,
439
+ locked_at: null
440
+ })
441
+ .where('status', 'pending')
442
+ .whereIn('automation_run_id', trx('automation_runs')
443
+ .select('id')
444
+ .where('automation_id', automationId))
445
+ .where((builder) => {
446
+ builder
447
+ .whereNull('locked_by')
448
+ .orWhere('locked_at', '<', staleLockCutoff);
449
+ });
450
+ }
451
+ async function isStepRunAutomationActive(trx, stepId) {
452
+ const query = trx('automation_run_steps as step')
453
+ .select(trx.raw('1'))
454
+ .innerJoin('automation_runs as run', 'run.id', 'step.automation_run_id')
455
+ .innerJoin('automations as automation', 'automation.id', 'run.automation_id')
456
+ .where('step.id', stepId)
457
+ .where('automation.status', 'active');
458
+ return await selectExists(trx, query);
459
+ }
460
+ async function isRunAutomationActive(trx, automationRunId) {
461
+ const query = trx('automation_runs as run')
462
+ .select(trx.raw('1'))
463
+ .innerJoin('automations as automation', 'automation.id', 'run.automation_id')
464
+ .where('run.id', automationRunId)
465
+ .where('automation.status', 'active');
466
+ return await selectExists(trx, query);
467
+ }
468
+ async function selectExists(trx, query) {
469
+ const row = await trx
470
+ .select(trx.raw('exists ? as `exists`', [query]))
471
+ .first();
472
+ return Boolean(Number(row?.exists));
473
+ }
419
474
  /**
420
475
  * Update a step. Returns whether the update succeeded.
421
476
  *
@@ -17,7 +17,7 @@ import type {
17
17
  EditAutomationData,
18
18
  Page
19
19
  } from './automations-repository';
20
- import {LOCK_TIMEOUT_MS} from './constants';
20
+ import {getStaleLockCutoff} from './stale-lock-cutoff';
21
21
  import type {ExclusifyUnion, ReadonlyDeep} from 'type-fest';
22
22
 
23
23
  const HOUR_MS = 60 * 60 * 1000;
@@ -165,14 +165,20 @@ export function createDatabaseAutomationsRepository({
165
165
  return null;
166
166
  }
167
167
 
168
+ const now = new Date();
169
+
168
170
  const updatedAutomation = await updateAutomation(trx, {
169
171
  ...automation,
170
172
  status: data.status,
171
- updated_at: toDatabaseDate(new Date())
173
+ updated_at: toDatabaseDate(now)
172
174
  });
173
175
 
174
176
  await replaceAutomationGraph(trx, updatedAutomation.id, data.actions, data.edges);
175
177
 
178
+ if (updatedAutomation.status === 'inactive') {
179
+ await cancelCancelablePendingStepsForAutomation(trx, updatedAutomation.id, now);
180
+ }
181
+
176
182
  return await buildAutomation(trx, updatedAutomation);
177
183
  });
178
184
  },
@@ -375,7 +381,7 @@ async function fetchAndLockSteps(trx: Knex.Transaction, limit: number): Promise<
375
381
 
376
382
  const now = new Date();
377
383
  const nowString = toDatabaseDate(now);
378
- const staleLockCutoff = new Date(now.getTime() - LOCK_TIMEOUT_MS);
384
+ const staleLockCutoff = getStaleLockCutoff(now);
379
385
  const staleLockCutoffString = toDatabaseDate(staleLockCutoff);
380
386
  const lockId = crypto.randomUUID();
381
387
 
@@ -561,6 +567,10 @@ async function finishStepAndEnqueueNext(
561
567
  return null;
562
568
  }
563
569
 
570
+ if (!await isRunAutomationActive(trx, step.automation_run_id)) {
571
+ return null;
572
+ }
573
+
564
574
  const next = await findNextActionRevision(trx, step.action_id);
565
575
 
566
576
  if (!next) {
@@ -620,6 +630,11 @@ async function retryStep(
620
630
  step: Pick<AutomationStepToRun, 'id' | 'locked_by'>,
621
631
  retryAt: Readonly<Date>
622
632
  ): Promise<boolean> {
633
+ if (!await isStepRunAutomationActive(trx, step.id)) {
634
+ await markStepTerminal(trx, step, 'automation disabled');
635
+ return false;
636
+ }
637
+
623
638
  const nowString = toDatabaseDate(new Date());
624
639
  return await updateStep(trx, step, {
625
640
  status: 'pending',
@@ -655,6 +670,58 @@ function getReadyAtForAction(
655
670
  }
656
671
  }
657
672
 
673
+ async function cancelCancelablePendingStepsForAutomation(
674
+ trx: Knex.Transaction,
675
+ automationId: string,
676
+ now: Readonly<Date>
677
+ ): Promise<void> {
678
+ const nowString = toDatabaseDate(now);
679
+ const staleLockCutoff = toDatabaseDate(getStaleLockCutoff(now));
680
+ await trx('automation_run_steps')
681
+ .update({
682
+ status: 'automation disabled',
683
+ finished_at: nowString,
684
+ updated_at: nowString,
685
+ locked_by: null,
686
+ locked_at: null
687
+ })
688
+ .where('status', 'pending')
689
+ .whereIn('automation_run_id', trx('automation_runs')
690
+ .select('id')
691
+ .where('automation_id', automationId))
692
+ .where((builder) => {
693
+ builder
694
+ .whereNull('locked_by')
695
+ .orWhere('locked_at', '<', staleLockCutoff);
696
+ });
697
+ }
698
+
699
+ async function isStepRunAutomationActive(trx: Knex.Transaction, stepId: string): Promise<boolean> {
700
+ const query = trx('automation_run_steps as step')
701
+ .select(trx.raw('1'))
702
+ .innerJoin('automation_runs as run', 'run.id', 'step.automation_run_id')
703
+ .innerJoin('automations as automation', 'automation.id', 'run.automation_id')
704
+ .where('step.id', stepId)
705
+ .where('automation.status', 'active');
706
+ return await selectExists(trx, query);
707
+ }
708
+
709
+ async function isRunAutomationActive(trx: Knex.Transaction, automationRunId: string): Promise<boolean> {
710
+ const query = trx('automation_runs as run')
711
+ .select(trx.raw('1'))
712
+ .innerJoin('automations as automation', 'automation.id', 'run.automation_id')
713
+ .where('run.id', automationRunId)
714
+ .where('automation.status', 'active');
715
+ return await selectExists(trx, query);
716
+ }
717
+
718
+ async function selectExists(trx: Knex.Transaction, query: Knex.QueryBuilder): Promise<boolean> {
719
+ const row = await trx
720
+ .select<{exists: boolean | number | string}>(trx.raw('exists ? as `exists`', [query]))
721
+ .first();
722
+ return Boolean(Number(row?.exists));
723
+ }
724
+
658
725
  /**
659
726
  * Update a step. Returns whether the update succeeded.
660
727
  *
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getStaleLockCutoff = getStaleLockCutoff;
4
+ const constants_1 = require("./constants");
5
+ function getStaleLockCutoff(now) {
6
+ return new Date(now.getTime() - constants_1.LOCK_TIMEOUT_MS);
7
+ }
@@ -0,0 +1,5 @@
1
+ import {LOCK_TIMEOUT_MS} from './constants';
2
+
3
+ export function getStaleLockCutoff(now: Readonly<Date>): Date {
4
+ return new Date(now.getTime() - LOCK_TIMEOUT_MS);
5
+ }
@@ -1,4 +1,3 @@
1
- /* eslint-disable ghost/filenames/match-exported-class */
2
1
 
3
2
  export interface EmailAddress {
4
3
  address: string;