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.
- package/MigratorConfig.js +0 -1
- package/bin/create-migration.js +1 -1
- package/bin/generate-golden-email.js +0 -1
- package/components/tryghost-i18n-0.0.0.tgz +0 -0
- package/core/boot.js +13 -7
- package/core/built/admin/assets/{PolarAngleAxis-CLluELUg.js → PolarAngleAxis-DufzOBEz.js} +1 -1
- package/core/built/admin/assets/{_baseAssignValue-Dz1iqdwD.js → _baseAssignValue-BjUm4QeA.js} +1 -1
- package/core/built/admin/assets/{a-large-small-BA74vHPp.js → a-large-small-D0RF_WS9.js} +1 -1
- package/core/built/admin/assets/{account-migration-B-NqdKuN.js → account-migration-B7n6ezrZ.js} +1 -1
- package/core/built/admin/assets/activitypub/{account-migration-ClmVrAXr.js → account-migration-BJeRnMCl.js} +3 -3
- package/core/built/admin/assets/activitypub/activitypub.js +1 -1
- package/core/built/admin/assets/activitypub/{at-sign-9F3qd4Nn.js → at-sign-BygVOKdj.js} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-jBHA13Ej.js → avatar-flipboard-CNcjI8lK.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-1irhvVtF.js → bluesky-sharing-mgBNeRfA.js} +4 -4
- package/core/built/admin/assets/activitypub/{copy-DXRvsGiK.js → copy-BMChXIQc.js} +2 -2
- package/core/built/admin/assets/activitypub/{deleted-feed-item-67VQJde4.js → deleted-feed-item-D-spb6V1.js} +2 -2
- package/core/built/admin/assets/activitypub/{edit-profile-BfpbEj98.js → edit-profile-CDQ3V8-c.js} +4 -4
- package/core/built/admin/assets/activitypub/{feed-DUrI7SPC.js → feed-Vqr3WU-V.js} +4 -4
- package/core/built/admin/assets/activitypub/{hash-CMtB10PN.js → hash-D7SSXVOg.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-2xq8SJ7A.js → inbox-BHk5Dw27.js} +2 -2
- package/core/built/admin/assets/activitypub/{index-BT97xLpd.js → index-BejkeH-m.js} +2 -2
- package/core/built/admin/assets/activitypub/{index-DssHQAjK.js → index-BqL23J-m.js} +32 -32
- package/core/built/admin/assets/activitypub/{index-Ba6FXt8y.js → index-CHhvjNuG.js} +6 -6
- package/core/built/admin/assets/activitypub/{index-ERtz4Bvy.js → index-CZsNO9hA.js} +5 -5
- package/core/built/admin/assets/activitypub/{index-sgY7lLrg.js → index-CaIhRYzz.js} +3 -3
- package/core/built/admin/assets/activitypub/{index-D1EWoXrK.js → index-DA2MkQVu.js} +2 -2
- package/core/built/admin/assets/activitypub/{index-sudOvKcY.js → index-DHhFcmfu.js} +21 -21
- package/core/built/admin/assets/activitypub/{index-Ccighz1t.js → index-sh2bDSls.js} +4 -4
- package/core/built/admin/assets/activitypub/{label-KQSsSjmN.js → label-D80yhOIm.js} +2 -2
- package/core/built/admin/assets/activitypub/{moderation-B78if-r0.js → moderation-LJNmKBCi.js} +3 -3
- package/core/built/admin/assets/activitypub/{note-Bfr7ovn7.js → note-CyjKLcQ-.js} +4 -4
- package/core/built/admin/assets/activitypub/{reply-CehesW8Z.js → reply-BiiCFx7G.js} +2 -2
- package/core/built/admin/assets/activitypub/{separator-B_Ly_Qq8.js → separator-CY3PMmtI.js} +2 -2
- package/core/built/admin/assets/activitypub/{settings-D6R97p7v.js → settings-BQy6Gr7R.js} +3 -3
- package/core/built/admin/assets/activitypub/{step-1-Cl1YxgZ1.js → step-1-DCqRwgf3.js} +3 -3
- package/core/built/admin/assets/activitypub/{step-2-DDyR--Qe.js → step-2-DQlhehTO.js} +5 -5
- package/core/built/admin/assets/activitypub/{step-3-Don8gv0n.js → step-3-DAUCAcxW.js} +7 -7
- package/core/built/admin/assets/activitypub/{tabs-DTpfm-wu.js → tabs-B_6UuI7r.js} +3 -3
- package/core/built/admin/assets/activitypub/{topic-filter-Oee10885.js → topic-filter--FT1A_dc.js} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
- package/core/built/admin/assets/admin-x-settings/{code-editor-view-CGZuiDGx.js → code-editor-view-Bp_sCJR0.js} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-CwEblQZC.js → index-BPXA3pTW.js} +5 -5
- package/core/built/admin/assets/admin-x-settings/{index-rr_UntGE.js → index-BS375mg1.js} +3 -3
- package/core/built/admin/assets/admin-x-settings/{index-BpM6W2br.js → index-B_PlQoEG.js} +3 -3
- package/core/built/admin/assets/admin-x-settings/{index-BGdS5xw4.js → index-BflhAbu2.js} +3 -3
- package/core/built/admin/assets/admin-x-settings/{index-DtWUeJhp.js → index-CftMe8fe.js} +3 -3
- package/core/built/admin/assets/admin-x-settings/{index-B65inCwj.js → index-DRB7FH6y.js} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-DSX261S9.js → index-DijZ3oAM.js} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-DF6YEs01.js → index-cV0Lof9H.js} +1526 -1500
- package/core/built/admin/assets/admin-x-settings/{index-CluZYkUC.js → index-qc4x3vZj.js} +3 -3
- package/core/built/admin/assets/admin-x-settings/{modals-Bf4KOU31.js → modals-CkjuEByw.js} +55 -67
- package/core/built/admin/assets/{arrow-right-BxP3aeA5.js → arrow-right-BF77Yx73.js} +1 -1
- package/core/built/admin/assets/{at-sign-Bf0y-jim.js → at-sign-DhJF3n_V.js} +1 -1
- package/core/built/admin/assets/{audience-DHR1WgFK.js → audience-DHPKKayV.js} +1 -1
- package/core/built/admin/assets/{automated-emails-DbJbDLib.js → automated-emails-DInKYTEC.js} +1 -1
- package/core/built/admin/assets/{automations-aLgTzSsZ.js → automations-DJpeZw9T.js} +1 -1
- package/core/built/admin/assets/automations-DpQKN9j0.js +1 -0
- package/core/built/admin/assets/{avatar-flipboard-B0F2LMW5.js → avatar-flipboard-yRdmFC6R.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-DjhtIuyr.js → bluesky-sharing-qSiCERoR.js} +1 -1
- package/core/built/admin/assets/{chart-BGwxlD0z.js → chart-CP2N9ZC-.js} +1 -1
- package/core/built/admin/assets/{chevron-up-C2FeVzre.js → chevron-up-BQ6fOpFR.js} +1 -1
- package/core/built/admin/assets/{chunk.524.31b31f7958dfae110016.js → chunk.524.ad4560630d4d62fbceb5.js} +4 -4
- package/core/built/admin/assets/{chunk.582.a855020953886af6e83b.js → chunk.582.fd4878e4e0af31ef86c0.js} +6 -6
- package/core/built/admin/assets/{chunk.972.258e6b1dab79b2189f1c.js → chunk.98.69968e0f393d82c2949c.js} +4 -4
- package/core/built/admin/assets/{code-editor-view-DzPkm_nJ.js → code-editor-view-CVODiRVJ.js} +1 -1
- package/core/built/admin/assets/{comments-DgItXUWN.js → comments-CP-NxopD.js} +1 -1
- package/core/built/admin/assets/{content-helpers-BNefU_PE.js → content-helpers-Crzkusiq.js} +1 -1
- package/core/built/admin/assets/{copy-DoNNUZlb.js → copy-r1LFsU75.js} +1 -1
- package/core/built/admin/assets/{data-list-CO1cZQRU.js → data-list-CzSc7z-2.js} +1 -1
- package/core/built/admin/assets/{deleted-feed-item-DC2wSAhv.js → deleted-feed-item-PyEM8FiX.js} +1 -1
- package/core/built/admin/assets/{dropzone-BTODLafn.js → dropzone-DI8A9qO6.js} +1 -1
- package/core/built/admin/assets/{edit-profile-BMMpmWIZ.js → edit-profile-DlJ64lV7.js} +1 -1
- package/core/built/admin/assets/editor-CevNZicQ.js +8 -0
- package/core/built/admin/assets/{empty-indicator-Dza0FQlj.js → empty-indicator-Dxg05yWg.js} +1 -1
- package/core/built/admin/assets/{en-yf9qAqTu.js → en-D93GVp3o.js} +1 -1
- package/core/built/admin/assets/{eye-CAqnnfQF.js → eye-6POPPpTw.js} +1 -1
- package/core/built/admin/assets/{feed-BkNsbWiF.js → feed-P_RMYb5x.js} +1 -1
- package/core/built/admin/assets/{field-Ddb1ZOqM.js → field-DeNjwWrL.js} +1 -1
- package/core/built/admin/assets/{filter-query-core-5Mac92kh.js → filter-query-core-DFs5WSmA.js} +1 -1
- package/core/built/admin/assets/{filters-BCb4bgtg.js → filters-C_BSQgb7.js} +1 -1
- package/core/built/admin/assets/{gh-chart-C6zkIg8k.js → gh-chart-pNHAbeQn.js} +1 -1
- package/core/built/admin/assets/{ghost-5313258fd425ced5db6516acde01ba36.js → ghost-3f5b3569e0beec61266cb74077677fbc.js} +3 -3
- package/core/built/admin/assets/{growth-ezUlN8OE.js → growth-B83tVOYO.js} +1 -1
- package/core/built/admin/assets/{hash-DE2OmChI.js → hash-BCTATV5W.js} +1 -1
- package/core/built/admin/assets/{inbox-OthiHvlV.js → inbox-rE3c5suT.js} +1 -1
- package/core/built/admin/assets/{index-BLyzLfyx.js → index-7EiRc5Ay.js} +1 -1
- package/core/built/admin/assets/{index-CV5v0eFE.js → index-B5bpZGTI.js} +1 -1
- package/core/built/admin/assets/index-BNtzAd7H.css +1 -0
- package/core/built/admin/assets/{index-BC1yWeaJ.js → index-BT33w2Io.js} +1 -1
- package/core/built/admin/assets/{index-DjffQ3VJ.js → index-BbkXWdhb.js} +1 -1
- package/core/built/admin/assets/{index-DH6PA3W6.js → index-BfhSrnrJ.js} +1 -1
- package/core/built/admin/assets/{index-CRvK0Iac.js → index-BmR_OUpb.js} +1 -1
- package/core/built/admin/assets/{index-CNV7fxKZ.js → index-BuhMgn5c.js} +1 -1
- package/core/built/admin/assets/{index-pkixJbdq.js → index-C-075SzJ.js} +34 -34
- package/core/built/admin/assets/{index-DJoByVS8.js → index-CB3hJd9E.js} +1 -1
- package/core/built/admin/assets/{index-D3bb-MEJ.js → index-CCISLIQd.js} +1 -1
- package/core/built/admin/assets/{index-BIEhcKiV.js → index-Cnptpx11.js} +1 -1
- package/core/built/admin/assets/{index-DK1cu59e.js → index-CrBRU81r.js} +1 -1
- package/core/built/admin/assets/{index-B0_p1-R0.js → index-CscUHFJp.js} +1 -1
- package/core/built/admin/assets/{index-Bi5qzf86.js → index-D3ZzfuJt.js} +1 -1
- package/core/built/admin/assets/{index-CKPny_6u.js → index-D4wn-U0V.js} +1 -1
- package/core/built/admin/assets/{index-YzcsD17L.js → index-DNRlkUeu.js} +1 -1
- package/core/built/admin/assets/{index-BoMsDCxD.js → index-D_G6negZ.js} +1 -1
- package/core/built/admin/assets/{index-CwsmINtV.js → index-DfxHSK1s.js} +1 -1
- package/core/built/admin/assets/{index-X91I8PcS.js → index-Dpc3COTl.js} +1 -1
- package/core/built/admin/assets/{index-c1h-EQpd.js → index-UsbnU2jy.js} +1 -1
- package/core/built/admin/assets/{index-oKgNVmwC.js → index-VfrJzh5m.js} +1 -1
- package/core/built/admin/assets/{index-D5MrQBSe.js → index-fbxc8Dpt.js} +1 -1
- package/core/built/admin/assets/{input-group-BOLMrDfJ.js → input-group-BLygwiDK.js} +1 -1
- package/core/built/admin/assets/{koenig-lexical-DlzcWUPw.js → koenig-lexical-B9U3sBS5.js} +1 -1
- package/core/built/admin/assets/{kpi-card-BlF7JKRC.js → kpi-card-Bmnvvp4X.js} +1 -1
- package/core/built/admin/assets/{kpi-card-HDshm3lO.js → kpi-card-CGVtzdgJ.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-Dty6zMSP.js → kpi-tabs-DxbvMkIU.js} +1 -1
- package/core/built/admin/assets/{kpis-CFz4r5wW.js → kpis-CQBAGVjP.js} +1 -1
- package/core/built/admin/assets/{label-CVzNcbjb.js → label-DpTwHQi_.js} +1 -1
- package/core/built/admin/assets/{links-D9dHwDlP.js → links-Bdw25FJF.js} +1 -1
- package/core/built/admin/assets/{list-page-shoQED7N.js → list-page-BvTQwP5F.js} +1 -1
- package/core/built/admin/assets/{loader-circle-cad701gH.js → loader-circle-3HJkQKWf.js} +1 -1
- package/core/built/admin/assets/{mail-DG4PKpZy.js → mail-DvrqP73n.js} +1 -1
- package/core/built/admin/assets/{main-layout-BmFla3Se.js → main-layout-BAPpm0bz.js} +1 -1
- package/core/built/admin/assets/{members-d19Z6k1C.js → members-CF4CzJSf.js} +1 -1
- package/core/built/admin/assets/minus-DJIePlnU.js +1 -0
- package/core/built/admin/assets/modals-CVsVi0zu.js +35 -0
- package/core/built/admin/assets/{moderation-BWBLnh4h.js → moderation-BX1KH5QL.js} +1 -1
- package/core/built/admin/assets/{newsletter-DNY4MroI.js → newsletter-BlKeb58e.js} +1 -1
- package/core/built/admin/assets/{newsletters-B10iBPP-.js → newsletters-3In8H0Z5.js} +1 -1
- package/core/built/admin/assets/{note-S0b6wjRR.js → note-ybwJCAxm.js} +1 -1
- package/core/built/admin/assets/{offers-B-9mZqKd.js → offers-Cl0ZNEMr.js} +1 -1
- package/core/built/admin/assets/{onboarding-route-AMFn0X5G.js → onboarding-route-A3ECeMAi.js} +1 -1
- package/core/built/admin/assets/{overview-WKadyZRW.js → overview-DrMrfsk-.js} +1 -1
- package/core/built/admin/assets/{pagemenu-BofQcFaF.js → pagemenu-O7HbsehH.js} +1 -1
- package/core/built/admin/assets/{pencil-LfMcD0vS.js → pencil-CknTbJeE.js} +1 -1
- package/core/built/admin/assets/{pin-CayUjL8e.js → pin-BNs-JdQ3.js} +1 -1
- package/core/built/admin/assets/{post-analytics-DMHvt3Jl.js → post-analytics-Dk1VloZp.js} +1 -1
- package/core/built/admin/assets/{post-analytics-context-10cqioP1.js → post-analytics-context-kPL94Ivz.js} +1 -1
- package/core/built/admin/assets/{post-analytics-header-DlICE18K.js → post-analytics-header-CpMOozq-.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-Cfwy2UwD.js → post-share-modal-F0dobdhF.js} +1 -1
- package/core/built/admin/assets/posts/{alert-dialog-cQK32v9G.js → alert-dialog-NoPLULhG.js} +5 -5
- package/core/built/admin/assets/posts/{app-utils-DQ2hixop.js → app-utils-BiM9QO4L.js} +3 -3
- package/core/built/admin/assets/posts/{automations-BqkU9-zY.js → automations-BgIAmq9f.js} +3 -3
- package/core/built/admin/assets/posts/{automations-CoilKpVH.js → automations-D_XwO405.js} +30 -30
- package/core/built/admin/assets/posts/{avatar-BejAO6Rj.js → avatar-BYsYOpmq.js} +5 -5
- package/core/built/admin/assets/posts/{badge-BYGRyd2f.js → badge-B54vLono.js} +2 -2
- package/core/built/admin/assets/posts/{button-CG2Dpjqy.js → button-DMLfwOQt.js} +3 -3
- package/core/built/admin/assets/posts/{check-fsC5BvGC.js → check-D6qDi62T.js} +2 -2
- package/core/built/admin/assets/posts/{comments-CsyF5C1S.js → comments-CyYCbvUG.js} +26 -26
- package/core/built/admin/assets/posts/{data-list-JslRF2F5.js → data-list-B0DrMm-x.js} +4 -4
- package/core/built/admin/assets/posts/{dialog-3KY3A8t5.js → dialog-DtEZpY1q.js} +3 -3
- package/core/built/admin/assets/posts/{editor-3HPW_xW4.js → editor-LJHLmUoW.js} +1943 -1943
- package/core/built/admin/assets/posts/{ellipsis-BhK5S70U.js → ellipsis-JeIF0Biz.js} +2 -2
- package/core/built/admin/assets/posts/{empty-indicator-C9DbE9wZ.js → empty-indicator-dktzN32y.js} +2 -2
- package/core/built/admin/assets/posts/{filters-BCBDCnQF.js → filters-D1L-LU_m.js} +10 -10
- package/core/built/admin/assets/posts/{get-site-timezone-83C41MGz.js → get-site-timezone-dsyl8nlB.js} +3 -3
- package/core/built/admin/assets/posts/{growth-COoL1kMe.js → growth-yX8UB_ce.js} +12 -12
- package/core/built/admin/assets/posts/{heading-D3VqNO3z.js → heading-CNte43wH.js} +2 -2
- package/core/built/admin/assets/posts/{hooks-5YpJYWOw.js → hooks-BIgv7QBp.js} +2 -2
- package/core/built/admin/assets/posts/{index-Dqn5WIFh.js → index-2FO6Vy0r.js} +11 -11
- package/core/built/admin/assets/posts/{input-C1qcZBGw.js → input-BycmlCp8.js} +3 -3
- package/core/built/admin/assets/posts/{input-surface-C4vUAdbV.js → input-surface-vY3ACaso.js} +2 -2
- package/core/built/admin/assets/posts/{koenig-lexical-NZjxFY0X.js → koenig-lexical-Cq8SUcZC.js} +2 -2
- package/core/built/admin/assets/posts/{kpis-C2FEXoUT.js → kpis-CAJcU1w0.js} +27 -27
- package/core/built/admin/assets/posts/{label-CmJzmJY7.js → label-BmLTflMF.js} +3 -3
- package/core/built/admin/assets/posts/{links-nzud7t_t.js → links-BVcmMZmF.js} +4 -4
- package/core/built/admin/assets/posts/{list-page-D_rv4TgN.js → list-page-DX2WRn5T.js} +4 -4
- package/core/built/admin/assets/posts/{loading-indicator-DAOD6O2b.js → loading-indicator-C6FD4nS6.js} +3 -3
- package/core/built/admin/assets/posts/{mail-CtH0gTpq.js → mail-BMyoNUVM.js} +2 -2
- package/core/built/admin/assets/posts/{main-layout-VqvCxOXQ.js → main-layout-ClnxdW1Y.js} +2 -2
- package/core/built/admin/assets/posts/{message-circle-D4bCjxKS.js → message-circle-DwfHX-p1.js} +2 -2
- package/core/built/admin/assets/posts/{newsletter-BfwFyv6d.js → newsletter-Bid5_Bim.js} +19 -19
- package/core/built/admin/assets/posts/{overview-DKGum6ro.js → overview-BXti6Wg_.js} +18 -18
- package/core/built/admin/assets/posts/{pencil-Cffx-vm3.js → pencil-ChTk8IpW.js} +2 -2
- package/core/built/admin/assets/posts/{plus-D2ekwsq0.js → plus-DIKls8C6.js} +2 -2
- package/core/built/admin/assets/posts/{popover-DdbdKflP.js → popover-DVZsAT5u.js} +4 -4
- package/core/built/admin/assets/posts/{post-analytics-BleJYoNZ.js → post-analytics-DBL_kS0g.js} +6 -6
- package/core/built/admin/assets/posts/{post-analytics-context-DT3Avn9Q.js → post-analytics-context-Dr5y5nxd.js} +6 -6
- package/core/built/admin/assets/posts/{post-analytics-header-BktlrM_v.js → post-analytics-header-LH2-Jzgp.js} +13 -13
- package/core/built/admin/assets/posts/{post-share-modal-G8v3ZuSg.js → post-share-modal-D4DrfNfJ.js} +6 -6
- package/core/built/admin/assets/posts/{posts-CdWV_nlm.js → posts-ByxhcCrn.js} +2 -2
- package/core/built/admin/assets/posts/posts.js +1 -1
- package/core/built/admin/assets/posts/{search-CXP00fG8.js → search-DZR23LP-.js} +2 -2
- package/core/built/admin/assets/posts/{settings-DEXxDYc2.js → settings-CWyOKKnK.js} +2 -2
- package/core/built/admin/assets/posts/{sheet-CXHmD_eT.js → sheet-CNe7II5c.js} +3 -3
- package/core/built/admin/assets/posts/{site-C6DajagV.js → site-H9RAiQVv.js} +2 -2
- package/core/built/admin/assets/posts/{skeleton-Bihnkn8q.js → skeleton-Cn6x1x_C.js} +3 -3
- package/core/built/admin/assets/posts/{source-icon-C3oQy3UI.js → source-icon-BZOYujd1.js} +4 -4
- package/core/built/admin/assets/posts/{stats-DFicPtCX.js → stats-ugHCg8Oe.js} +4 -4
- package/core/built/admin/assets/posts/{table-DSJmeU5t.js → table-dG3Q5X95.js} +2 -2
- package/core/built/admin/assets/posts/{tabs-oRdVpN8P.js → tabs--EW8uL5B.js} +4 -4
- package/core/built/admin/assets/posts/{tags-6v5Qt_05.js → tags-DVdOfiSf.js} +2 -2
- package/core/built/admin/assets/posts/{tags-Upnkm0CF.js → tags-i0HJqNYU.js} +16 -16
- package/core/built/admin/assets/posts/{tooltip-Ci7pesWi.js → tooltip-ZvsT1Fs9.js} +4 -4
- package/core/built/admin/assets/posts/{trash-2-NCYWjRlg.js → trash-2-BT9fEHUL.js} +2 -2
- package/core/built/admin/assets/posts/{value-CcwzOh2_.js → value-DoFcsWDk.js} +2 -2
- package/core/built/admin/assets/posts/{virtual-list-window-CNahQBXY.js → virtual-list-window-Bt47XODn.js} +3 -3
- package/core/built/admin/assets/posts/{web-C3KrPq0T.js → web-BMTRLT-h.js} +20 -20
- package/core/built/admin/assets/posts/{zap-k30mZEuO.js → zap-DcIfEzKX.js} +2 -2
- package/core/built/admin/assets/{posts-DAfLuxAA.js → posts-DODOCB1E.js} +1 -1
- package/core/built/admin/assets/{power-Drkzg1xX.js → power-BZ7BM82m.js} +1 -1
- package/core/built/admin/assets/{referrers-TYxTJ2On.js → referrers-xwNb6L-8.js} +1 -1
- package/core/built/admin/assets/{repeat-CTM1qAzl.js → repeat-DHMCQMCA.js} +1 -1
- package/core/built/admin/assets/{reply-DXN16g7W.js → reply-DoLURZi2.js} +1 -1
- package/core/built/admin/assets/{rocket-DLixlWs0.js → rocket-BDH89dZr.js} +1 -1
- package/core/built/admin/assets/{select-DbiLEqYb.js → select-oE39QRll.js} +1 -1
- package/core/built/admin/assets/{send-B71C_46D.js → send-QgRN_yRy.js} +1 -1
- package/core/built/admin/assets/{settings-BBMPA3MP.js → settings-CF4NLxuS.js} +4 -4
- package/core/built/admin/assets/{settings-lSFHFrsJ.js → settings-D7e6KLAi.js} +1 -1
- package/core/built/admin/assets/{share-modal-CXrYja6o.js → share-modal-BxIh7i7o.js} +1 -1
- package/core/built/admin/assets/{sort-button-WYo-9Kge.js → sort-button-CEe_40Pi.js} +1 -1
- package/core/built/admin/assets/{source-icon-D6BzjYPP.js → source-icon-CzTNLO6j.js} +1 -1
- package/core/built/admin/assets/{sprout-DVfG_qhy.js → sprout-B2fN41p-.js} +1 -1
- package/core/built/admin/assets/{square-BbUJwQJi.js → square-CJZsjE-3.js} +1 -1
- package/core/built/admin/assets/{stats-CnXALQ4j.js → stats-jTxCK1CB.js} +1 -1
- package/core/built/admin/assets/{stats-view-B-yeMUkl.js → stats-view-B4jz69N8.js} +1 -1
- package/core/built/admin/assets/{step-1-DUl_hlpV.js → step-1-BovjhDNc.js} +1 -1
- package/core/built/admin/assets/{step-2-DjX_A4Ke.js → step-2-ClG40BfX.js} +1 -1
- package/core/built/admin/assets/{step-3-CnDCCA8T.js → step-3-ZMSyEHZI.js} +1 -1
- package/core/built/admin/assets/{table-AagTvMa9.js → table-B8Pj96or.js} +1 -1
- package/core/built/admin/assets/{tabs-IDJWnQD-.js → tabs-BZosOk0o.js} +1 -1
- package/core/built/admin/assets/{tags-CXAcIDw-.js → tags-BpWoYBfB.js} +1 -1
- package/core/built/admin/assets/{tags-C3lykbgu.js → tags-DZukMyuU.js} +1 -1
- package/core/built/admin/assets/{textarea-Cc3nUXep.js → textarea-BtnzsOk7.js} +1 -1
- package/core/built/admin/assets/{thumbs-down-SsjC2lMD.js → thumbs-down-CZTzyz7D.js} +1 -1
- package/core/built/admin/assets/{tiers-J8AeZmRd.js → tiers-Do7zBqAk.js} +1 -1
- package/core/built/admin/assets/{toggle-group-BzD8yimX.js → toggle-group-abk9FYXi.js} +1 -1
- package/core/built/admin/assets/{topic-filter-CA_L6ehz.js → topic-filter-CdMvjkmc.js} +1 -1
- package/core/built/admin/assets/{trash-BKhRqxsX.js → trash-DNXRsBf0.js} +1 -1
- package/core/built/admin/assets/{underline-_HIgX6e1.js → underline-HXWuVCAi.js} +1 -1
- package/core/built/admin/assets/{undo-2-ClkkOS0O.js → undo-2-C2ntNJKC.js} +1 -1
- package/core/built/admin/assets/{upload-DEPHbyvo.js → upload-D52l7HIb.js} +1 -1
- package/core/built/admin/assets/{use-growth-stats-BM2qWt0C.js → use-growth-stats-Dgs0UxDT.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-zyklfekn.js → use-simple-pagination-Cj2rVggA.js} +1 -1
- package/core/built/admin/assets/{user-round-check-DoaGU2hC.js → user-round-check-C0GdT99c.js} +1 -1
- package/core/built/admin/assets/{user-round-x-CwEKNuU6.js → user-round-x-CVCvxuUG.js} +1 -1
- package/core/built/admin/assets/{virtual-list-window-C0Cv0gUC.js → virtual-list-window-mdQS2bt_.js} +1 -1
- package/core/built/admin/assets/{wallet-cards-BjdG5glh.js → wallet-cards-CtLF01jQ.js} +1 -1
- package/core/built/admin/assets/{web-BZNnanCT.js → web-DTib8_n8.js} +1 -1
- package/core/built/admin/index.html +6 -6
- package/core/frontend/helpers/ghost_foot.js +12 -2
- package/core/frontend/helpers/prev_post.js +1 -1
- package/core/frontend/helpers/tpl/gift-toast.hbs +130 -0
- package/core/frontend/services/proxy.js +9 -0
- package/core/frontend/services/routing/controllers/entry/canonical-url.js +15 -0
- package/core/frontend/services/routing/controllers/entry/canonical-url.ts +14 -0
- package/core/frontend/services/routing/controllers/entry/gift-links.js +82 -0
- package/core/frontend/services/routing/controllers/entry/gift-links.ts +92 -0
- package/core/frontend/services/routing/controllers/entry/markdown.js +62 -0
- package/core/frontend/services/routing/controllers/entry/markdown.ts +66 -0
- package/core/frontend/services/routing/controllers/entry.js +96 -105
- package/core/frontend/services/routing/controllers/entry.ts +97 -0
- package/core/frontend/services/routing/controllers/index.js +1 -1
- package/core/frontend/web/middleware/frontend-caching.js +9 -0
- package/core/server/adapters/cache/Redis.js +0 -1
- package/core/server/adapters/redirects/RedirectsStoreBase.d.ts +0 -4
- package/core/server/adapters/scheduling/scheduling-default.js +8 -1
- package/core/server/api/endpoints/gift-links.js +23 -16
- package/core/server/api/endpoints/gift-links.ts +25 -17
- package/core/server/api/endpoints/index.js +0 -1
- package/core/server/api/endpoints/previews.js +3 -24
- package/core/server/api/endpoints/utils/serializers/output/gift-links.js +4 -4
- package/core/server/api/endpoints/utils/serializers/output/gift-links.ts +4 -4
- package/core/server/api/endpoints/utils/serializers/output/index.js +0 -1
- package/core/server/api/endpoints/utils/validators/input/automation_email_previews.js +8 -9
- package/core/server/api/endpoints/utils/validators/input/index.js +0 -1
- package/core/server/data/migrations/versions/6.48/2026-06-23-00-00-00-drop-gift-links-revoked-at.js +6 -0
- 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
- package/core/server/data/migrations/versions/6.48/2026-06-24-00-00-00-drop-gift-links-redemption-columns.js +10 -0
- package/core/server/data/schema/fixtures/fixtures.json +2 -2
- package/core/server/data/schema/schema.js +1 -4
- package/core/server/data/seeders/data-generator.js +4 -1
- package/core/server/data/seeders/importers/members-login-events-importer.js +0 -1
- package/core/server/data/seeders/importers/members-newsletters-importer.js +0 -1
- package/core/server/data/seeders/importers/members-paid-subscription-events-importer.js +0 -1
- package/core/server/data/seeders/importers/members-status-events-importer.js +0 -1
- package/core/server/data/seeders/importers/members-stripe-customers-importer.js +0 -1
- package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +0 -1
- package/core/server/data/seeders/importers/members-subscribe-events-importer.js +0 -1
- package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +0 -1
- package/core/server/lib/lexical.js +62 -36
- package/core/server/lib/post-revisions.js +0 -2
- package/core/server/lib/post-revisions.ts +0 -3
- package/core/server/models/index.js +0 -3
- package/core/server/services/auth/reset-authentication.js +0 -2
- package/core/server/services/auth/reset-authentication.ts +0 -3
- package/core/server/services/auth/session/index.js +0 -1
- package/core/server/services/automations/automations-api.js +59 -17
- package/core/server/services/automations/automations-api.ts +69 -16
- package/core/server/services/automations/database-automations-repository.js +58 -3
- package/core/server/services/automations/database-automations-repository.ts +70 -3
- package/core/server/services/automations/stale-lock-cutoff.js +7 -0
- package/core/server/services/automations/stale-lock-cutoff.ts +5 -0
- package/core/server/services/email-address/email-address-parser.js.d.ts +0 -1
- package/core/server/services/email-service/batch-sending-service.js +99 -26
- package/core/server/services/email-service/email-service-wrapper.js +7 -2
- package/core/server/services/email-service/email-service.js +146 -4
- package/core/server/services/gift-links/database.js +0 -3
- package/core/server/services/gift-links/database.ts +1 -4
- package/core/server/services/gift-links/index.js +2 -2
- package/core/server/services/gift-links/index.ts +4 -2
- package/core/server/services/gift-links/models.js +0 -2
- package/core/server/services/gift-links/models.ts +0 -2
- package/core/server/services/gift-links/queries.js +2 -2
- package/core/server/services/gift-links/queries.ts +2 -2
- package/core/server/services/gift-links/serializers.js +0 -2
- package/core/server/services/gift-links/serializers.ts +0 -2
- package/core/server/services/gift-links/service.js +54 -35
- package/core/server/services/gift-links/service.ts +70 -35
- package/core/server/services/gifts/gift-reminder-scheduler.js +0 -2
- package/core/server/services/gifts/gift-reminder-scheduler.ts +0 -2
- package/core/server/services/indexnow.js +8 -1
- package/core/server/services/internal-keys/index.js +0 -1
- package/core/server/services/internal-keys/index.ts +0 -2
- package/core/server/services/lib/dynamic-redirect-manager.d.ts +0 -1
- package/core/server/services/lib/in-memory-repository.js +0 -1
- package/core/server/services/lib/in-memory-repository.ts +0 -2
- package/core/server/services/members/api.js +0 -3
- package/core/server/services/members/create-paid-member-shim.js +31 -0
- package/core/server/services/members/create-paid-member-shim.ts +31 -0
- package/core/server/services/members/emails/signin.js +0 -1
- package/core/server/services/members/members-api/members-api.js +0 -2
- package/core/server/services/members/members-api/repositories/member-repository.js +0 -3
- package/core/server/services/members/service.js +4 -0
- package/core/server/services/post-scheduling/index.js +0 -2
- package/core/server/services/post-scheduling/index.ts +0 -2
- package/core/server/services/post-scheduling/post-scheduling.js +0 -5
- package/core/server/services/post-scheduling/post-scheduling.ts +0 -5
- package/core/server/services/recommendations/service/bookshelf-repository.js +0 -1
- package/core/server/services/recommendations/service/bookshelf-repository.ts +0 -4
- package/core/server/services/recommendations/service/incoming-recommendation-service.js +0 -1
- package/core/server/services/recommendations/service/incoming-recommendation-service.ts +0 -1
- package/core/server/services/recommendations/service/recommendation-controller.js +0 -1
- package/core/server/services/recommendations/service/recommendation-controller.ts +0 -1
- package/core/server/services/recommendations/service/recommendation-metadata-service.js +0 -1
- package/core/server/services/recommendations/service/recommendation-metadata-service.ts +0 -1
- package/core/server/services/recommendations/service/recommendation.js +0 -1
- package/core/server/services/recommendations/service/recommendation.ts +0 -1
- package/core/server/services/remote-flags/index.js +0 -1
- package/core/server/services/remote-flags/index.ts +0 -1
- package/core/server/services/slack.js +5 -1
- package/core/server/services/update-check/index.js +0 -1
- package/core/server/services/url/lazy-find-resource.js +0 -1
- package/core/server/services/url/lazy-find-resource.ts +0 -2
- package/core/server/services/url/lazy-url-service.js +0 -1
- package/core/server/services/url/lazy-url-service.ts +0 -2
- package/core/server/services/url/permalink-matcher.js +0 -1
- package/core/server/services/url/permalink-matcher.ts +0 -2
- package/core/server/services/url/router-filter.js +0 -2
- package/core/server/services/url/router-filter.ts +0 -2
- package/core/server/services/url/url-service-facade.js +0 -1
- package/core/server/services/url/url-service-facade.ts +0 -2
- package/core/server/services/verification/verification-webhook-service.js +0 -1
- package/core/server/services/verification/verification-webhook-service.ts +0 -1
- package/core/server/web/api/endpoints/admin/routes.js +7 -7
- package/core/shared/events-ts/post-deleted-event.js +0 -2
- package/core/shared/events-ts/post-deleted-event.ts +0 -2
- package/core/shared/labs.js +0 -1
- package/package.json +8 -16
- package/pnpm-lock.yaml +162 -113
- package/core/built/admin/assets/automations-BthXBzzq.js +0 -1
- package/core/built/admin/assets/editor-91xrI2A3.js +0 -8
- package/core/built/admin/assets/index-BYb09f-7.css +0 -1
- package/core/built/admin/assets/minus-DmrVsJ-z.js +0 -1
- package/core/built/admin/assets/modals-dnqxCS3l.js +0 -35
- package/core/server/models/outbox.js +0 -24
- package/core/server/services/outbox/events/start-outbox-processing-event.js +0 -22
- package/core/server/services/outbox/handlers/member-created.js +0 -85
- package/core/server/services/outbox/index.js +0 -49
- package/core/server/services/outbox/jobs/index.js +0 -30
- package/core/server/services/outbox/jobs/lib/constants.js +0 -17
- package/core/server/services/outbox/jobs/lib/process-entries.js +0 -117
- package/core/server/services/outbox/jobs/lib/process-outbox.js +0 -96
- package/core/server/services/outbox/jobs/outbox-job.js +0 -36
- 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()
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
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()
|
|
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
|
-
|
|
171
|
+
return isEmptyParsedLexical(parsed);
|
|
172
|
+
} catch {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
153
176
|
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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 {
|
|
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(
|
|
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 =
|
|
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
|
+
}
|