ghost 6.68.0 → 6.69.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/components/ghost-storage-base-3.0.1.tgz +0 -0
- package/components/tryghost-adapter-base-cache-0.2.1.tgz +0 -0
- package/components/tryghost-adapter-base-scheduling-0.2.5.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.4.tgz +0 -0
- package/components/tryghost-app-contracts-0.0.0.tgz +0 -0
- package/components/tryghost-checkout-0.0.0.tgz +0 -0
- package/components/tryghost-i18n-0.0.0.tgz +0 -0
- package/components/tryghost-kg-card-factory-5.2.6.tgz +0 -0
- package/components/tryghost-kg-clean-basic-html-4.3.6.tgz +0 -0
- package/components/tryghost-kg-converters-1.2.6.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.7.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.2.3.tgz +0 -0
- package/components/tryghost-kg-default-transforms-1.3.6.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.4.1.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.5.1.tgz +0 -0
- package/components/tryghost-kg-markdown-html-renderer-7.2.7.tgz +0 -0
- package/components/tryghost-kg-utils-1.1.6.tgz +0 -0
- package/components/tryghost-metafield-csv-0.0.0.tgz +0 -0
- package/components/tryghost-metafield-types-0.1.1.tgz +0 -0
- package/core/boot.js +6 -0
- package/core/built/admin/assets/{about-D_nwtf7X.js → about-Dxp30cNg.js} +1 -1
- package/core/built/admin/assets/{account-migration-BaecWw1T.js → account-migration-D05akW4q.js} +1 -1
- package/core/built/admin/assets/activitypub/{account-migration-4saTEZ6p.js → account-migration-BmiwuzXI.js} +6 -6
- package/core/built/admin/assets/activitypub/activitypub.js +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-BHBNuZQH.js → bluesky-sharing-B1fzMdFb.js} +6 -6
- package/core/built/admin/assets/activitypub/{content-formatters-DVx9YktY.js → content-formatters-G0qBT5QE.js} +3 -3
- package/core/built/admin/assets/activitypub/{domain-rAmlMXBC.js → domain-B3UtdA1m.js} +7 -7
- package/core/built/admin/assets/activitypub/{edit-profile-CXYL4ZrS.js → edit-profile-9EodzviR.js} +4 -4
- package/core/built/admin/assets/activitypub/{explore-dqpEx3GA.js → explore-DmwPdxHI.js} +8 -8
- package/core/built/admin/assets/activitypub/{feed-Cb-wYNw9.js → feed-yauqnu7A.js} +5 -5
- package/core/built/admin/assets/activitypub/{field-JxegcPt6.js → field-CsrMjbg2.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-2hXDJBn5.js → inbox-DV3RrpFH.js} +7 -7
- package/core/built/admin/assets/activitypub/{label-DRQzZ7lb.js → label-DPs40NLn.js} +2 -2
- package/core/built/admin/assets/activitypub/layout-CvOhndCA.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-BnYKcTXY.js → moderation-DGa6hVNH.js} +7 -7
- package/core/built/admin/assets/activitypub/{note-C6ucS3Wu.js → note-BVL6LVHF.js} +5 -5
- package/core/built/admin/assets/activitypub/{notifications-DgBW9wEC.js → notifications-CRUu_uMC.js} +5 -5
- package/core/built/admin/assets/activitypub/{onboarding-DGu6yG4L.js → onboarding-Dpm-nLkQ.js} +2 -2
- package/core/built/admin/assets/activitypub/{onboarding-Bq3fNf_a.js → onboarding-KtpJA-xb.js} +2 -2
- package/core/built/admin/assets/activitypub/{preferences-C3jI1eVN.js → preferences-CdFuVqIt.js} +7 -7
- package/core/built/admin/assets/activitypub/{profile-Cn3vRb5Z.js → profile-KnNAoHYu.js} +8 -8
- package/core/built/admin/assets/activitypub/{routes-DbrwlS9h.js → routes-BRndai50.js} +26 -26
- package/core/built/admin/assets/activitypub/{settings-D0BALDQu.js → settings-DU7ftJ8w.js} +4 -4
- package/core/built/admin/assets/activitypub/{site-BFGj8HuQ.js → site-D1ZYV3AT.js} +2 -2
- package/core/built/admin/assets/activitypub/{step-1-CaoPg0a5.js → step-1-9IHheE78.js} +4 -4
- package/core/built/admin/assets/activitypub/{step-3-5k0TkyC9.js → step-3-DBERnfa-.js} +3 -3
- package/core/built/admin/assets/activitypub/{tabs-BV4JU7Vg.js → tabs-CvkwStij.js} +4 -4
- package/core/built/admin/assets/activitypub/{topic-filter-71sX5nfN.js → topic-filter-CS0Jsf_4.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-B3srUuKt.js → use-activity-pub-queries-CGgtQrxL.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-Cl6bGNy7.js → use-reply-chain-data-CLdUpx1t.js} +4 -4
- package/core/built/admin/assets/activitypub/{users-EcSPpds5.js → users-CyoMXlwX.js} +11 -3
- package/core/built/admin/assets/{add-integration-modal-DBFzeAUT.js → add-integration-modal-BVg-ec8i.js} +1 -1
- package/core/built/admin/assets/{add-newsletter-modal-CYJe-dTC.js → add-newsletter-modal-Du6th521.js} +1 -1
- package/core/built/admin/assets/{add-recommendation-modal-CgwGKH-_.js → add-recommendation-modal-BiCJ0-q5.js} +1 -1
- package/core/built/admin/assets/{analytics-CcXT_WqF.js → analytics-CQJzDDdJ.js} +1 -1
- package/core/built/admin/assets/{announcement-bar-modal-Ldcg-PB-.js → announcement-bar-modal-CskyZLzT.js} +1 -1
- package/core/built/admin/assets/{api-BLteQZ7W.js → api-BFocMbDg.js} +2 -2
- package/core/built/admin/assets/{api-keys-CoeerpwI.js → api-keys-CqKJHuAV.js} +1 -1
- package/core/built/admin/assets/{audience-Deg9lzfk.js → audience-Dn7LuH-r.js} +1 -1
- package/core/built/admin/assets/{auth-layout-CRaMX7g6.js → auth-layout-VLhXgXn5.js} +1 -1
- package/core/built/admin/assets/{automated-emails-WcjjRkRV.js → automated-emails-D5cFl1wr.js} +1 -1
- package/core/built/admin/assets/{automations-CbIdX0rd.js → automations-BpweKEcq.js} +1 -1
- package/core/built/admin/assets/automations-DBTYwNTJ.js +1 -0
- package/core/built/admin/assets/{bluesky-sharing-BJMNRwnU.js → bluesky-sharing-CrfpFHQ9.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-D-3N170i.js → chart-helpers-BSuFLd6-.js} +1 -1
- package/core/built/admin/assets/{chunk.524.94ab17e1199b4c3f4615.js → chunk.524.e24d68bc6253b274b726.js} +4 -4
- package/core/built/admin/assets/{chunk.582.6f09b19a007e2805824e.js → chunk.582.47834285406dfa1abb29.js} +6 -6
- package/core/built/admin/assets/{chunk.476.53b5794a67d68111e0bf.js → chunk.837.e1454abc054e1fba1fe5.js} +5 -5
- package/core/built/admin/assets/{code-editor-view-DhTwpxxv.js → code-editor-view-BrGe3eS4.js} +1 -1
- package/core/built/admin/assets/{code-editor-view-C7wXGiuv.js → code-editor-view-CY6JCXNv.js} +1 -1
- package/core/built/admin/assets/{color-picker-field-Cjz2zY11.js → color-picker-field-DGe9HH6W.js} +1 -1
- package/core/built/admin/assets/{comments-DY-VLVxh.js → comments-iRbF6XiJ.js} +1 -1
- package/core/built/admin/assets/{config-B3HuCo0y.js → config-CRAZ4Civ.js} +1 -1
- package/core/built/admin/assets/{content-api-modal-2jt-jWiM.js → content-api-modal-B-XRbzeW.js} +1 -1
- package/core/built/admin/assets/{content-formatters-VqvNA7Pv.js → content-formatters-C08JJbUJ.js} +2 -2
- package/core/built/admin/assets/{context-menu-BWbk9nd5.js → context-menu-BxGXsgQO.js} +1 -1
- package/core/built/admin/assets/{custom-field-type-option-CIatEwIN.js → custom-field-type-option-C8Nt4X6x.js} +1 -1
- package/core/built/admin/assets/{custom-integration-modal-Cw0w2Tb3.js → custom-integration-modal-DzI16qjN.js} +1 -1
- package/core/built/admin/assets/{date-range-select-O-uA-BRV.js → date-range-select-BZqsYsGx.js} +1 -1
- package/core/built/admin/assets/{design-and-theme-modal-BLjNHRYN.js → design-and-theme-modal-1ty_KEkM.js} +2 -2
- package/core/built/admin/assets/{dist-DJhCsld6.js → dist-B4jqKAG5.js} +1 -1
- package/core/built/admin/assets/{dist-5Ol0DC_t.js → dist-Bmo2j_KH.js} +1 -1
- package/core/built/admin/assets/{dist-p52UDdoR.js → dist-C-_aTlvJ.js} +1 -1
- package/core/built/admin/assets/{dist-CcN1a5SB.js → dist-CWHIoxYn.js} +1 -1
- package/core/built/admin/assets/{dist-BcfsaiCV.js → dist-Cp2M5TzT.js} +1 -1
- package/core/built/admin/assets/{dist-kjTYGivb.js → dist-DQAAPbtg.js} +1 -1
- package/core/built/admin/assets/{dist-IMek6Acg.js → dist-DyO279y5.js} +1 -1
- package/core/built/admin/assets/{domain-DaPAeOFF.js → domain-DiFl_Z1A.js} +1 -1
- package/core/built/admin/assets/{edit-profile-BKO5qonH.js → edit-profile-PrLbJV3P.js} +1 -1
- package/core/built/admin/assets/editor-BBJDJL8U.js +8 -0
- package/core/built/admin/assets/{editor-screen-l4BUPE2o.js → editor-screen-nblrp808.js} +3 -3
- package/core/built/admin/assets/{email-previews-B1Ska3CF.js → email-previews-BKJxrW29.js} +1 -1
- package/core/built/admin/assets/{email-sending-status-provider-Bjn5KTy2.js → email-sending-status-provider-DB9ylBWk.js} +1 -1
- package/core/built/admin/assets/{emails-qxNbhO6X.js → emails-8YiSmC2X.js} +1 -1
- package/core/built/admin/assets/{embed-signup-form-modal-DSgds6Js.js → embed-signup-form-modal-CZmbYKfd.js} +1 -1
- package/core/built/admin/assets/{error-boundary-CJODV8Rg.js → error-boundary-DrxPFFO-.js} +1 -1
- package/core/built/admin/assets/{esm-CNLDtBMc.js → esm-_iCUCvbN.js} +1 -1
- package/core/built/admin/assets/{explore-C6V_JpUZ.js → explore-C69LSRVg.js} +1 -1
- package/core/built/admin/assets/{feed-B_TyFn9E.js → feed-B_0m7WhZ.js} +1 -1
- package/core/built/admin/assets/{feedback-gKOzmiGt.js → feedback-DW1DXdzT.js} +1 -1
- package/core/built/admin/assets/{filters-Bf6E1qAP.js → filters-Dr3vMnjZ.js} +1 -1
- package/core/built/admin/assets/{filters-DhsiEotO.js → filters-M2sRWPoF.js} +1 -1
- package/core/built/admin/assets/{first-promoter-modal-LmTXHr-z.js → first-promoter-modal-D43R2zhI.js} +1 -1
- package/core/built/admin/assets/{focus-koenig-editor-on-bottom-click-BAcLNJti.js → focus-koenig-editor-on-bottom-click-BzS8jiJf.js} +1 -1
- package/core/built/admin/assets/fullscreen-close-button-CWSp-MsK.js +1 -0
- package/core/built/admin/assets/{ghost-63bc7da4336844000c523a5a0819531e.js → ghost-ffde29676517b768b1aef5ec8d492418.js} +93 -96
- package/core/built/admin/assets/{gift-link-modal-uZtwRHzl.js → gift-link-modal-CNE7dfPD.js} +1 -1
- package/core/built/admin/assets/{gift-links-HVqaXear.js → gift-links-lQ7H_D8Z.js} +1 -1
- package/core/built/admin/assets/global-search-modal-DzRM3aON.js +1 -0
- package/core/built/admin/assets/{growth-BlPW4DHj.js → growth-CAi_5HIf.js} +1 -1
- package/core/built/admin/assets/{growth-CflU3vR0.js → growth-CMHbTWKE.js} +1 -1
- package/core/built/admin/assets/history-modal-BIz13vhr.js +1 -0
- package/core/built/admin/assets/{hooks-C-BDQ6Hs.js → hooks-0r7qxGEo.js} +2 -2
- package/core/built/admin/assets/{html-field-D6G0G1d_.js → html-field-vTe2q1be.js} +1 -1
- package/core/built/admin/assets/{iframe-buffering-DW-Axhvc.js → iframe-buffering-cu-yK7NA.js} +1 -1
- package/core/built/admin/assets/{images-BSUWxquu.js → images-DTZtaIAr.js} +1 -1
- package/core/built/admin/assets/{inbox-BkU3sg13.js → inbox-BHfpmmzn.js} +1 -1
- package/core/built/admin/assets/{index-z8-nEVdb.css → index-B02LJqc8.css} +1 -1
- package/core/built/admin/assets/{index-CuDhPtkB.js → index-RuRs_qGS.js} +3 -3
- package/core/built/admin/assets/{index.es-H6HOI4DG.js → index.es-rdb1skp_.js} +1 -1
- package/core/built/admin/assets/{integrations-BRo5DwA5.js → integrations-Deg9-u8X.js} +1 -1
- package/core/built/admin/assets/{invite-user-modal-CydBOG99.js → invite-user-modal-C4DeNcoO.js} +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +10483 -10455
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +131 -131
- package/core/built/admin/assets/{koenig-lexical-CG_eb4dw.js → koenig-lexical-BhXu1__-.js} +96 -96
- package/core/built/admin/assets/{kpi-helpers-CyRx_yD6.js → kpi-helpers-H-Hl6h7h.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-V1_Nu9sh.js → kpi-tabs-k4A9-imn.js} +1 -1
- package/core/built/admin/assets/{label-picker-GTIIlBQ6.js → label-picker-DaivLdMw.js} +1 -1
- package/core/built/admin/assets/{labels-BVGLgdAj.js → labels-BCj6D_nl.js} +1 -1
- package/core/built/admin/assets/{layout-CG58T6P7.js → layout-_g2xhB0L.js} +1 -1
- package/core/built/admin/assets/{lib-Dkic19kH.js → lib-Bre6UXdJ.js} +2 -2
- package/core/built/admin/assets/{links-jD8AkkIu.js → links-C8RnEJVJ.js} +1 -1
- package/core/built/admin/assets/{member-activity-JggQ0VNQ.js → member-activity-DNJPUW5X.js} +1 -1
- package/core/built/admin/assets/{member-activity-feed-BBeCZmLM.js → member-activity-feed-CvKEetHS.js} +1 -1
- package/core/built/admin/assets/{member-custom-fields-DPsyP1Hq.js → member-custom-fields-CBdiwPD9.js} +1 -1
- package/core/built/admin/assets/{member-detail-D7qugPN7.js → member-detail-BIoyIIsl.js} +1 -1
- package/core/built/admin/assets/{members-Cj_BUZUL.js → members-CH6KeXQc.js} +1 -1
- package/core/built/admin/assets/{members-alDWPKDd.js → members-Dd9RczFK.js} +1 -1
- package/core/built/admin/assets/{migrate-2uKKakld.js → migrate-CVQIWsXO.js} +1 -1
- package/core/built/admin/assets/{moderation-pCUKsxbF.js → moderation-OtjZoNOc.js} +1 -1
- package/core/built/admin/assets/{navigation-modal-D0FBI5-m.js → navigation-modal-Bl02NjtO.js} +1 -1
- package/core/built/admin/assets/{newsletter-4urgCSR_.js → newsletter-BXyT7o69.js} +1 -1
- package/core/built/admin/assets/{newsletter-detail-modal-Chh1jvjH.js → newsletter-detail-modal-DxuKNu_1.js} +1 -1
- package/core/built/admin/assets/{newsletter-emails-CzVCiC2K.js → newsletter-emails-DicgmnMU.js} +1 -1
- package/core/built/admin/assets/{newsletters-eRQTtOBH.js → newsletters-By25FmUs.js} +1 -1
- package/core/built/admin/assets/{newsletters-BMEgeL2l.js → newsletters-CACa0VCx.js} +1 -1
- package/core/built/admin/assets/{note-dqog4Bua.js → note-CF4LrSmR.js} +1 -1
- package/core/built/admin/assets/{notifications-rcEVv-gx.js → notifications-BmHJmqIJ.js} +1 -1
- package/core/built/admin/assets/{offers-hV7zBtsI.js → offers-Df9Jzay5.js} +1 -1
- package/core/built/admin/assets/{offers-container-modal-DgZgkMsA.js → offers-container-modal-R0HRwugm.js} +1 -1
- package/core/built/admin/assets/{onboarding-4TxbZb90.js → onboarding-Bpo1yTzA.js} +1 -1
- package/core/built/admin/assets/{onboarding-SGscZLIc.js → onboarding-bMq3R8jB.js} +1 -1
- package/core/built/admin/assets/{onboarding-route-B3mN9l7E.js → onboarding-route-flf5YWti.js} +1 -1
- package/core/built/admin/assets/{overview-D4EanvDG.js → overview-C9WvmmOC.js} +1 -1
- package/core/built/admin/assets/{overview-CCwbHet8.js → overview-CDkHdIJM.js} +1 -1
- package/core/built/admin/assets/{page-header-Baymg6VR.js → page-header-CxTb0je5.js} +1 -1
- package/core/built/admin/assets/{pagemenu--UL0gW1x.js → pagemenu-DXr_U43J.js} +1 -1
- package/core/built/admin/assets/{pages-Cbq1M2kG.js → pages-Bm-zFXyP.js} +1 -1
- package/core/built/admin/assets/pages-route-LCw94aIO.js +1 -0
- package/core/built/admin/assets/{pintura-modal-D6HGlnJa.js → pintura-modal-B5sIPWcj.js} +1 -1
- package/core/built/admin/assets/{portal-modal-Bhqsevn_.js → portal-modal-DjOO3Unv.js} +1 -1
- package/core/built/admin/assets/{post-analytics-header-DX9fETdi.js → post-analytics-header-D2anehCj.js} +1 -1
- package/core/built/admin/assets/{post-analytics-provider-U7RtOle1.js → post-analytics-provider-BLQvV5C0.js} +1 -1
- package/core/built/admin/assets/{post-analytics-BJWDtC3a.js → post-analytics-wdu9gq8u.js} +1 -1
- package/core/built/admin/assets/{post-debug-D1b8Amju.js → post-debug-BnABXMfD.js} +1 -1
- package/core/built/admin/assets/{posts-Lg-p_ZHg.js → posts-CftunZgV.js} +1 -1
- package/core/built/admin/assets/{posts-list-screen-CmsW18e_.js → posts-list-screen-CwXmvY68.js} +2 -2
- package/core/built/admin/assets/posts-route-J74fMhCe.js +1 -0
- package/core/built/admin/assets/{preferences-CMlDfHfa.js → preferences-DFhf_pJF.js} +1 -1
- package/core/built/admin/assets/{preview-modal-D8om_cQv.js → preview-modal-BrZza3R_.js} +1 -1
- package/core/built/admin/assets/{profile-yFsIoZom.js → profile-CS4fP60N.js} +1 -1
- package/core/built/admin/assets/{radio-group-DwgLjwWB.js → radio-group-Xfp-frJz.js} +1 -1
- package/core/built/admin/assets/{referrers-DzP5QoXL.js → referrers-C8LJlrBm.js} +1 -1
- package/core/built/admin/assets/{reload-CZ-KP3IQ.js → reload-D0fnKoCm.js} +1 -1
- package/core/built/admin/assets/{reset-CCAa8v35.js → reset-CB-ATcLE.js} +1 -1
- package/core/built/admin/assets/{restore-screen-Ba0i2Y1K.js → restore-screen-EaZf4V8j.js} +1 -1
- package/core/built/admin/assets/{routes-DrnhmKTT.js → routes-DKfpFMfF.js} +2 -2
- package/core/built/admin/assets/search-index-sM3OjEUl.js +1 -0
- package/core/built/admin/assets/settings-BWzod-5X.js +4 -0
- package/core/built/admin/assets/{settings-lqWkfSmT.js → settings-DvgngbFt.js} +1 -1
- package/core/built/admin/assets/{setup-BopRPuaM.js → setup-CdHAchG5.js} +1 -1
- package/core/built/admin/assets/{signin-e8NgsU9M.js → signin-CUEEme88.js} +1 -1
- package/core/built/admin/assets/{signin-verify-CHkFJevb.js → signin-verify-CYRphSrj.js} +1 -1
- package/core/built/admin/assets/{signout-DRvQZuWa.js → signout-kLCJau9G.js} +1 -1
- package/core/built/admin/assets/{signup-DQHi7jZz.js → signup-J2Vjq7Gt.js} +1 -1
- package/core/built/admin/assets/{slack-modal-BuGESzgn.js → slack-modal-DKamr_94.js} +1 -1
- package/core/built/admin/assets/source-icon-DqBCPDIK.js +1 -0
- package/core/built/admin/assets/{stats-CHOikfoP.js → stats-BL_FJ7Ns.js} +1 -1
- package/core/built/admin/assets/{stats-view-DQZzKeEz.js → stats-view-BwbdkdOQ.js} +1 -1
- package/core/built/admin/assets/{step-1-C8XwHcKt.js → step-1-CLYhNmG9.js} +1 -1
- package/core/built/admin/assets/{step-2-BXaXAdwY.js → step-2-B2ujMlpa.js} +1 -1
- package/core/built/admin/assets/{step-3-DytbqpJi.js → step-3-DoaTWqqj.js} +1 -1
- package/core/built/admin/assets/{stripe-connect-modal-BhudjSeO.js → stripe-connect-modal-DytxH9DJ.js} +1 -1
- package/core/built/admin/assets/{tabs-CDb_TceL.js → tabs-fHs3A8WC.js} +1 -1
- package/core/built/admin/assets/{tag-detail-V72Zlro8.js → tag-detail-BcO46Ids.js} +2 -2
- package/core/built/admin/assets/{tag-picker-M3fC-yOF.js → tag-picker-Ct6NeqWK.js} +1 -1
- package/core/built/admin/assets/{tags-CXqE2A2T.js → tags-C1h3wCpW.js} +1 -1
- package/core/built/admin/assets/{tags-ZhqbPPc6.js → tags-OlmRJ6Ps.js} +1 -1
- package/core/built/admin/assets/{testimonials-modal-Cn8gvIFA.js → testimonials-modal-DMsQzSCB.js} +1 -1
- package/core/built/admin/assets/{themes-pOHHvnkt.js → themes-CPJSMo_W.js} +1 -1
- package/core/built/admin/assets/{tier-detail-modal-CeDla1OO.js → tier-detail-modal-DssSmhkF.js} +1 -1
- package/core/built/admin/assets/{tiers-D7aGzwZ-.js → tiers-DlvS8D6_.js} +1 -1
- package/core/built/admin/assets/{toggle-group-D7ly9OGa.js → toggle-group-BFWCtH3p.js} +1 -1
- package/core/built/admin/assets/{topic-filter-DQ3pr6AB.js → topic-filter-DAJ-DFPb.js} +1 -1
- package/core/built/admin/assets/{transistor-modal-4_Yx2vRo.js → transistor-modal-CtyFRsD0.js} +1 -1
- package/core/built/admin/assets/{unsplash-modal-Dm7R4k4k.js → unsplash-modal-DHnnTPmZ.js} +1 -1
- package/core/built/admin/assets/{use-activity-pub-queries-Gy9_locM.js → use-activity-pub-queries-BgR1USeY.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data-oJ2o0pSS.js → use-analytics-data-Byf83QE-.js} +1 -1
- package/core/built/admin/assets/{use-availability-BqL7Dvn_.js → use-availability-CgZC6L_V.js} +1 -1
- package/core/built/admin/assets/{use-check-theme-limit-error-CxC9uKJz.js → use-check-theme-limit-error-DjprzKAN.js} +1 -1
- package/core/built/admin/assets/use-definitions-CJuXLSob.js +1 -0
- package/core/built/admin/assets/{use-email-sending-status-BFAg5TIb.js → use-email-sending-status-7kopNcza.js} +1 -1
- package/core/built/admin/assets/{use-filter-params-BOo9yZ5j.js → use-filter-params-DQ7uX-Zp.js} +1 -1
- package/core/built/admin/assets/{use-filterable-api-BnNLaS_r.js → use-filterable-api-CDrrxoGD.js} +1 -1
- package/core/built/admin/assets/{use-growth-stats-BS62uyyn.js → use-growth-stats-CCpnBgqS.js} +1 -1
- package/core/built/admin/assets/{use-host-limits-CNh4AAEs.js → use-host-limits-BZx7rzRI.js} +1 -1
- package/core/built/admin/assets/{use-limiter-DPKxoO1_.js → use-limiter-gF219BEg.js} +1 -1
- package/core/built/admin/assets/{use-pintura-config-CyVO62ZV.js → use-pintura-config-D-pmGkn2.js} +1 -1
- package/core/built/admin/assets/{use-pintura-editor-B5VBPbiO.js → use-pintura-editor-D_4zhlGE.js} +1 -1
- package/core/built/admin/assets/{use-post-referrers-nUPolSk7.js → use-post-referrers-Bc8eq4uy.js} +1 -1
- package/core/built/admin/assets/{use-post-top-links-DCH4eO11.js → use-post-top-links-DjltnQuI.js} +1 -1
- package/core/built/admin/assets/{use-reply-chain-data-UMzFbDvh.js → use-reply-chain-data-BiV3Cl0B.js} +1 -1
- package/core/built/admin/assets/use-sending-eta-gE-2B0Zw.js +1 -0
- package/core/built/admin/assets/{use-setting-group-Bf3vY8br.js → use-setting-group-YP84CqrJ.js} +1 -1
- package/core/built/admin/assets/{use-staff-users-CcQzTHqH.js → use-staff-users-CnwfdSx1.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-CPy5ipAP.js → use-tinybird-query-BYbl5gKY.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-DVNHuhu_.js → use-tinybird-token-Dz37zvb7.js} +1 -1
- package/core/built/admin/assets/{use-unsaved-changes-guard-Bvcp_Rny.js → use-unsaved-changes-guard-BkCuSlvU.js} +1 -1
- package/core/built/admin/assets/{use-upgrade-route-Bsxw9ZLN.js → use-upgrade-route-C-V-Xfak.js} +1 -1
- package/core/built/admin/assets/{use-url-input-DPJ592FB.js → use-url-input-BeYKYMGh.js} +1 -1
- package/core/built/admin/assets/{user-detail-modal-6UY6GdNy.js → user-detail-modal-CSoj7vOC.js} +1 -1
- package/core/built/admin/assets/{users-CzgQKbfO.js → users-CKJaAxEJ.js} +1 -1
- package/core/built/admin/assets/{view-site-DA_-zfGq.js → view-site-SpMx4P7G.js} +1 -1
- package/core/built/admin/assets/{web-Cq5YiD2r.js → web-CZf-k4F0.js} +1 -1
- package/core/built/admin/assets/{web-DqFIrOxt.js → web-uAqgYPK9.js} +1 -1
- package/core/built/admin/assets/{zapier-modal-DgqU-OBB.js → zapier-modal-DENbgTyE.js} +1 -1
- package/core/built/admin/index.html +24 -24
- package/core/frontend/helpers/color_to_rgba.js +15 -13
- package/core/frontend/helpers/facebook_url.js +27 -15
- package/core/frontend/services/assets-minification/card-assets.js +1 -1
- package/core/frontend/services/helpers/register-ghost-helpers.js +2 -2
- package/core/server/api/endpoints/app-installations.js +36 -0
- package/core/server/api/endpoints/automations.js +1 -1
- package/core/server/api/endpoints/index.js +4 -0
- package/core/server/data/exporter/table-lists.js +2 -0
- package/core/server/data/migrations/versions/6.69/2026-10-07-06-20-43-add-app-installation-permissions.js +40 -0
- package/core/server/data/schema/fixtures/fixtures.json +21 -0
- package/core/server/data/schema/in-development.js +1 -1
- package/core/server/data/schema/schema.js +53 -0
- package/core/server/lib/after-commit.js +20 -0
- package/core/server/models/action.js +6 -0
- package/core/server/services/app-installations/actions.js +44 -0
- package/core/server/services/app-installations/codec.js +20 -0
- package/core/server/services/app-installations/index.js +42 -0
- package/core/server/services/app-installations/schema.js +52 -0
- package/core/server/services/app-installations/service.js +160 -0
- package/core/server/services/automations/README.md +3 -3
- package/core/server/services/automations/automations-api.js +1 -1
- package/core/server/services/comments/comments-service-emails.js +8 -6
- package/core/server/services/email-service/README.md +16 -0
- package/core/server/services/email-service/email-service-wrapper.js +1 -0
- package/core/server/services/email-service/email-service.js +94 -53
- package/core/server/services/email-service/sending-status-serializers.js +4 -1
- package/core/server/services/email-service/sending-status-service.js +13 -0
- package/core/server/services/email-service/sending-status.js +12 -1
- package/core/server/services/members/members-api/repositories/member-repository.js +5 -23
- package/core/server/services/posts/post-email-handler.js +61 -28
- package/core/server/services/posts/posts-service.js +27 -2
- package/core/server/services/service-inventory.yaml +93 -7
- package/core/server/services/stats/stats-service.js +1 -5
- package/core/server/services/tinybird-sync/index.js +0 -3
- package/core/server/services/tinybird-sync/tinybird-sync-service.js +2 -6
- package/core/server/web/api/endpoints/admin/routes.js +25 -12
- package/core/shared/labs.js +4 -6
- package/package.json +11 -10
- package/pnpm-lock.yaml +112 -100
- package/pnpm-workspace.yaml +14 -12
- package/components/ghost-storage-base-3.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
- package/components/tryghost-checkout-0.0.1.tgz +0 -0
- package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
- package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
- package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
- package/components/tryghost-kg-default-transforms-1.3.4.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
- package/components/tryghost-kg-markdown-html-renderer-7.2.6.tgz +0 -0
- package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
- package/components/tryghost-metafield-csv-0.0.1.tgz +0 -0
- package/core/built/admin/assets/activitypub/layout-QTbY0t8e.js +0 -1
- package/core/built/admin/assets/automations-DVToctfJ.js +0 -1
- package/core/built/admin/assets/editor-CMXR0YvR.js +0 -8
- package/core/built/admin/assets/fullscreen-close-button-DXssPeDB.js +0 -1
- package/core/built/admin/assets/global-search-modal-C8LVJmiz.js +0 -1
- package/core/built/admin/assets/history-modal-parBtjpz.js +0 -1
- package/core/built/admin/assets/pages-route-CDk3V9rq.js +0 -1
- package/core/built/admin/assets/posts-route-CexHhklY.js +0 -1
- package/core/built/admin/assets/settings-xqakrAWa.js +0 -4
- package/core/built/admin/assets/source-icon-BjVangBd.js +0 -1
- package/core/built/admin/assets/use-definitions-DMVU3mTG.js +0 -1
- package/core/built/admin/assets/use-sending-eta-BsnKdQhv.js +0 -1
|
@@ -35,6 +35,19 @@ class SendingStatusService {
|
|
|
35
35
|
}, batches),
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
/** The retry decision statusFor reports, without the recipient counts its progress needs. */
|
|
39
|
+
async retryEligibilityFor(emailId) {
|
|
40
|
+
const email = await this.#knex('emails').select('status').where('id', emailId).first();
|
|
41
|
+
if (!email) {
|
|
42
|
+
return 'not-failed';
|
|
43
|
+
}
|
|
44
|
+
if (sending_status_schema_1.StoredSendingStatus.parse(email.status) !== 'failed') {
|
|
45
|
+
return 'not-failed';
|
|
46
|
+
}
|
|
47
|
+
const batches = await this.#knex('email_batches').distinct('status').where('email_id', emailId);
|
|
48
|
+
const statuses = batches.map((batch) => sending_status_schema_1.StoredSendingStatus.parse(batch.status));
|
|
49
|
+
return (0, sending_status_1.isRetryable)(statuses) ? 'retryable' : 'unknown-outcome';
|
|
50
|
+
}
|
|
38
51
|
async #batchesFor(emailId, recipientAccounting) {
|
|
39
52
|
if (recipientAccounting) {
|
|
40
53
|
const rows = await this.#knex('email_batches')
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.EmailSendingStatus = exports.SendingStatus = exports.SendingProgress = exports.SendingPhase = void 0;
|
|
4
4
|
exports.buildSendingStatus = buildSendingStatus;
|
|
5
|
+
exports.isRetryable = isRetryable;
|
|
5
6
|
const zod_1 = require("zod");
|
|
6
7
|
const ETA_WINDOW_MS = 60_000;
|
|
7
8
|
const ETA_MIN_INTERVALS = 2;
|
|
@@ -14,7 +15,12 @@ exports.SendingProgress = zod_1.z.object({
|
|
|
14
15
|
exports.SendingStatus = zod_1.z.discriminatedUnion('status', [
|
|
15
16
|
zod_1.z.object({ status: exports.SendingPhase, progress: exports.SendingProgress }),
|
|
16
17
|
zod_1.z.object({ status: zod_1.z.literal('submitted'), progress: exports.SendingProgress }),
|
|
17
|
-
zod_1.z.object({
|
|
18
|
+
zod_1.z.object({
|
|
19
|
+
status: zod_1.z.literal('failed'),
|
|
20
|
+
progress: exports.SendingProgress,
|
|
21
|
+
failedDuring: exports.SendingPhase,
|
|
22
|
+
retryable: zod_1.z.boolean(),
|
|
23
|
+
}),
|
|
18
24
|
]);
|
|
19
25
|
exports.EmailSendingStatus = zod_1.z.object({
|
|
20
26
|
id: zod_1.z.string(),
|
|
@@ -49,6 +55,7 @@ function buildSendingStatus(email, batches) {
|
|
|
49
55
|
status: 'failed',
|
|
50
56
|
progress: { completed, total, estimatedSecondsRemaining: null },
|
|
51
57
|
failedDuring: phase,
|
|
58
|
+
retryable: isRetryable(batches.map((batch) => batch.status)),
|
|
52
59
|
};
|
|
53
60
|
}
|
|
54
61
|
const failedThisAttempt = batches.filter((batch) => failedDuringAttempt(batch, attemptStartedAt));
|
|
@@ -70,6 +77,10 @@ function buildSendingStatus(email, batches) {
|
|
|
70
77
|
},
|
|
71
78
|
};
|
|
72
79
|
}
|
|
80
|
+
// A submitting batch may already have been accepted by the provider.
|
|
81
|
+
function isRetryable(batchStatuses) {
|
|
82
|
+
return !batchStatuses.includes('submitting');
|
|
83
|
+
}
|
|
73
84
|
function sumRecipients(batches) {
|
|
74
85
|
return batches.reduce((sum, batch) => sum + (batch.recipientCount ?? 0), 0);
|
|
75
86
|
}
|
|
@@ -17,6 +17,7 @@ const { NotFoundError } = require('@tryghost/errors');
|
|
|
17
17
|
const validator = require('@tryghost/validator');
|
|
18
18
|
const crypto = require('crypto');
|
|
19
19
|
const { hasActiveOffer } = require('../utils/has-active-offer');
|
|
20
|
+
const { afterCommit } = require('../../../../lib/after-commit');
|
|
20
21
|
const {
|
|
21
22
|
StartAutomationsPollEvent,
|
|
22
23
|
} = require('../../../automations/events/start-automations-poll-event');
|
|
@@ -239,25 +240,6 @@ module.exports = class MemberRepository {
|
|
|
239
240
|
);
|
|
240
241
|
}
|
|
241
242
|
|
|
242
|
-
/**
|
|
243
|
-
* Runs `fn` once the work it follows has committed.
|
|
244
|
-
*
|
|
245
|
-
* Without a transaction each query commits as it runs, so `fn` runs now, and whatever it
|
|
246
|
-
* throws or returns is the caller's. Inside `options.transacting` it runs when that
|
|
247
|
-
* transaction commits, after the caller has moved on, and not at all if it rolls back;
|
|
248
|
-
* a rollback, or a failure in `fn`, goes to `onFailure`.
|
|
249
|
-
*
|
|
250
|
-
* @param {{transacting?: {executionPromise: Promise<unknown>}}} options
|
|
251
|
-
* @param {() => unknown} fn
|
|
252
|
-
* @param {(err: unknown) => void} onFailure
|
|
253
|
-
*/
|
|
254
|
-
afterCommit(options, fn, onFailure) {
|
|
255
|
-
if (!options?.transacting) {
|
|
256
|
-
return fn();
|
|
257
|
-
}
|
|
258
|
-
options.transacting.executionPromise.then(() => fn()).catch(onFailure);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
243
|
/**
|
|
262
244
|
* @param {Parameters<typeof DomainEvents.dispatch>[0]} event
|
|
263
245
|
* @param {object} options
|
|
@@ -266,8 +248,8 @@ module.exports = class MemberRepository {
|
|
|
266
248
|
* @returns {void}
|
|
267
249
|
*/
|
|
268
250
|
dispatchEvent(event, options) {
|
|
269
|
-
|
|
270
|
-
options,
|
|
251
|
+
afterCommit(
|
|
252
|
+
options?.transacting,
|
|
271
253
|
() => DomainEvents.dispatch(event),
|
|
272
254
|
(err) => {
|
|
273
255
|
let memberMessageFragment = '';
|
|
@@ -1104,8 +1086,8 @@ module.exports = class MemberRepository {
|
|
|
1104
1086
|
|
|
1105
1087
|
if (this._stripeAPIService.configured && member._changed.email) {
|
|
1106
1088
|
// Stripe can't be rolled back, so it only hears about an address Ghost has saved.
|
|
1107
|
-
await
|
|
1108
|
-
options,
|
|
1089
|
+
await afterCommit(
|
|
1090
|
+
options?.transacting,
|
|
1109
1091
|
() => this.updateStripeCustomerEmails(member),
|
|
1110
1092
|
(err) => {
|
|
1111
1093
|
logging.error(
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const { BadRequestError } = require('@tryghost/errors');
|
|
2
|
+
const logging = require('@tryghost/logging');
|
|
2
3
|
const tpl = require('@tryghost/tpl');
|
|
4
|
+
const {
|
|
5
|
+
RETRY_UNKNOWN_OUTCOME_CODE,
|
|
6
|
+
RETRY_NOT_FAILED_CODE,
|
|
7
|
+
} = require('../email-service/email-service');
|
|
3
8
|
|
|
4
9
|
const messages = {
|
|
5
10
|
invalidEmailSegment: "The email segment parameter doesn't contain a valid filter",
|
|
@@ -22,7 +27,7 @@ class PostEmailHandler {
|
|
|
22
27
|
* Validates email can be sent before saving the post (if an email will be sent)
|
|
23
28
|
*
|
|
24
29
|
* @param {import('@tryghost/api-framework').Frame} frame
|
|
25
|
-
* @returns {Promise<
|
|
30
|
+
* @returns {Promise<import('../email-service/email-service').EmailPreflight|null>}
|
|
26
31
|
*/
|
|
27
32
|
async validateBeforeSave(frame) {
|
|
28
33
|
const newStatus = frame.data.posts[0].status;
|
|
@@ -33,30 +38,36 @@ class PostEmailHandler {
|
|
|
33
38
|
|
|
34
39
|
const existingPost = await this.models.Post.findOne(
|
|
35
40
|
{ id: frame.options.id, status: 'all' },
|
|
36
|
-
{
|
|
41
|
+
{
|
|
42
|
+
columns: ['id', 'status', 'newsletter_id', 'email_recipient_filter'],
|
|
43
|
+
transacting: frame.options.transacting,
|
|
44
|
+
},
|
|
37
45
|
);
|
|
38
46
|
const previousStatus = existingPost?.get('status');
|
|
47
|
+
const existingNewsletterId = existingPost?.get('newsletter_id');
|
|
39
48
|
|
|
40
|
-
const hasNewsletter = frame.options.newsletter ||
|
|
49
|
+
const hasNewsletter = frame.options.newsletter || existingNewsletterId;
|
|
41
50
|
const sendingEmail = hasNewsletter && this.shouldSendEmail(newStatus, previousStatus);
|
|
42
51
|
|
|
43
52
|
if (!sendingEmail) {
|
|
44
53
|
return null;
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
// A post keeps the newsletter and audience it already has, as the Post model does on save
|
|
57
|
+
const emailRecipientFilter = existingNewsletterId
|
|
58
|
+
? existingPost.get('email_recipient_filter')
|
|
59
|
+
: frame.options.email_segment || 'all';
|
|
49
60
|
|
|
50
61
|
await this.validateEmailRecipientFilter(emailRecipientFilter);
|
|
51
62
|
|
|
52
63
|
const newsletter = await this.getNewsletter(frame, existingPost);
|
|
53
64
|
|
|
54
|
-
const { emailCount } = await this.emailService.checkCanSendEmail(
|
|
65
|
+
const { emailCount, csdEmailCount } = await this.emailService.checkCanSendEmail(
|
|
55
66
|
newsletter,
|
|
56
67
|
emailRecipientFilter,
|
|
57
68
|
);
|
|
58
69
|
|
|
59
|
-
return { newsletter, emailRecipientFilter, emailCount };
|
|
70
|
+
return { newsletter, emailRecipientFilter, emailCount, csdEmailCount };
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
/**
|
|
@@ -81,36 +92,42 @@ class PostEmailHandler {
|
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
/**
|
|
84
|
-
* Retrieves the newsletter
|
|
95
|
+
* Retrieves the newsletter the post will be saved with: the one it has, otherwise the one requested
|
|
85
96
|
*
|
|
86
97
|
* @param {import('@tryghost/api-framework').Frame} frame
|
|
87
98
|
* @param {Object|null} existingPost
|
|
88
99
|
* @returns {Promise<Object|null>}
|
|
89
100
|
*/
|
|
90
101
|
async getNewsletter(frame, existingPost) {
|
|
91
|
-
if (frame.options.newsletter) {
|
|
92
|
-
return this.models.Newsletter.findOne(
|
|
102
|
+
if (frame.options.newsletter && !existingPost?.get('newsletter_id')) {
|
|
103
|
+
return this.models.Newsletter.findOne(
|
|
104
|
+
{ slug: frame.options.newsletter },
|
|
105
|
+
{ transacting: frame.options.transacting },
|
|
106
|
+
);
|
|
93
107
|
}
|
|
94
108
|
|
|
95
109
|
if (existingPost?.get('newsletter_id')) {
|
|
96
|
-
return this.models.Newsletter.findOne(
|
|
110
|
+
return this.models.Newsletter.findOne(
|
|
111
|
+
{ id: existingPost.get('newsletter_id') },
|
|
112
|
+
{ transacting: frame.options.transacting },
|
|
113
|
+
);
|
|
97
114
|
}
|
|
98
115
|
|
|
99
116
|
return null;
|
|
100
117
|
}
|
|
101
118
|
|
|
102
119
|
/**
|
|
103
|
-
*
|
|
120
|
+
* Creates a new newsletter email inside the transaction that publishes the post, so the post
|
|
121
|
+
* is only published if its email is created. Sending, or retrying a failed email, waits for
|
|
122
|
+
* the commit.
|
|
104
123
|
*
|
|
105
|
-
* @param {Object} model - The post model
|
|
124
|
+
* @param {Object} model - The saved post model
|
|
106
125
|
* @param {Object} [options]
|
|
107
|
-
* @param {
|
|
108
|
-
* @param {
|
|
109
|
-
* @
|
|
110
|
-
* @param {number} [options.preflight.emailCount]
|
|
111
|
-
* @returns {Promise<void>}
|
|
126
|
+
* @param {import('../email-service/email-service').EmailPreflight|null} [options.preflight] - Result of validateBeforeSave, passed through to createEmail
|
|
127
|
+
* @param {object} [options.transacting]
|
|
128
|
+
* @returns {Promise<(() => Promise<void>)|undefined>} Sends the email; call it once the transaction has committed
|
|
112
129
|
*/
|
|
113
|
-
async createOrRetryEmail(model, { preflight } = {}) {
|
|
130
|
+
async createOrRetryEmail(model, { preflight, transacting } = {}) {
|
|
114
131
|
if (!model.get('newsletter_id')) {
|
|
115
132
|
return;
|
|
116
133
|
}
|
|
@@ -123,25 +140,41 @@ class PostEmailHandler {
|
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
const postEmail = model.relations.email;
|
|
126
|
-
let email;
|
|
127
143
|
|
|
128
144
|
if (!postEmail) {
|
|
129
|
-
email = await this.emailService.createEmail(model, { preflight });
|
|
130
|
-
} else if (postEmail.get('status') === 'failed') {
|
|
131
|
-
email = await this.#retryEmail(postEmail);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (email) {
|
|
145
|
+
const email = await this.emailService.createEmail(model, { preflight, transacting });
|
|
135
146
|
model.relations.email = email;
|
|
136
147
|
model.set('email', email);
|
|
148
|
+
return async () => {
|
|
149
|
+
await this.emailService.scheduleEmail(email);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (postEmail.get('status') === 'failed') {
|
|
154
|
+
// Retrying reads the post outside the transaction, so it must be published first.
|
|
155
|
+
return async () => {
|
|
156
|
+
const email = await this.#retryEmail(postEmail, model.id);
|
|
157
|
+
if (email) {
|
|
158
|
+
model.relations.email = email;
|
|
159
|
+
model.set('email', email);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
137
162
|
}
|
|
138
163
|
}
|
|
139
164
|
|
|
140
|
-
async #retryEmail(email) {
|
|
165
|
+
async #retryEmail(email, postId) {
|
|
141
166
|
try {
|
|
142
167
|
return await this.emailService.retryEmail(email);
|
|
143
168
|
} catch (err) {
|
|
144
|
-
if (err.code
|
|
169
|
+
if (err.code === RETRY_UNKNOWN_OUTCOME_CODE) {
|
|
170
|
+
// The post is already saved. An unknown delivery outcome only withholds
|
|
171
|
+
// the resend, leaving the email failed.
|
|
172
|
+
logging.warn(
|
|
173
|
+
`Post ${postId} was saved without retrying email ${email.id}: delivery outcome is unknown`,
|
|
174
|
+
);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (err.code !== RETRY_NOT_FAILED_CODE) {
|
|
145
178
|
throw err;
|
|
146
179
|
}
|
|
147
180
|
// Publishing is already committed. A concurrent retry that claimed the
|
|
@@ -6,6 +6,8 @@ const ObjectId = require('bson-objectid').default;
|
|
|
6
6
|
const pick = require('lodash/pick');
|
|
7
7
|
const DomainEvents = require('@tryghost/domain-events');
|
|
8
8
|
const PostEmailHandler = require('./post-email-handler');
|
|
9
|
+
const { afterCommit } = require('../../lib/after-commit');
|
|
10
|
+
const logging = require('@tryghost/logging');
|
|
9
11
|
const {
|
|
10
12
|
validateAdminApiBulkFilterTransformer,
|
|
11
13
|
} = require('../../api/endpoints/utils/api-filter-utils');
|
|
@@ -75,9 +77,32 @@ class PostsService {
|
|
|
75
77
|
async editPost(frame, options) {
|
|
76
78
|
const preflight = await this.postEmailHandler.validateBeforeSave(frame);
|
|
77
79
|
|
|
78
|
-
const
|
|
80
|
+
const save = async (transacting) => {
|
|
81
|
+
const model = await this.models.Post.edit(frame.data.posts[0], {
|
|
82
|
+
...frame.options,
|
|
83
|
+
transacting,
|
|
84
|
+
});
|
|
85
|
+
const sendEmail = await this.postEmailHandler.createOrRetryEmail(model, {
|
|
86
|
+
preflight,
|
|
87
|
+
transacting,
|
|
88
|
+
});
|
|
89
|
+
return { model, sendEmail };
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const { model, sendEmail } = frame.options.transacting
|
|
93
|
+
? await save(frame.options.transacting)
|
|
94
|
+
: await this.models.Post.transaction(save);
|
|
79
95
|
|
|
80
|
-
|
|
96
|
+
if (sendEmail && frame.options.transacting) {
|
|
97
|
+
// The caller commits later. Rolling back leaves nothing to send.
|
|
98
|
+
afterCommit(
|
|
99
|
+
frame.options.transacting,
|
|
100
|
+
() => sendEmail().catch((err) => logging.error(err)),
|
|
101
|
+
() => {},
|
|
102
|
+
);
|
|
103
|
+
} else {
|
|
104
|
+
await sendEmail?.();
|
|
105
|
+
}
|
|
81
106
|
|
|
82
107
|
const dto = model.toJSON(frame.options);
|
|
83
108
|
|
|
@@ -154,6 +154,50 @@ services:
|
|
|
154
154
|
- >-
|
|
155
155
|
Add same-instance settings-change and root export characterization before moving
|
|
156
156
|
construction to boot.
|
|
157
|
+
app-installations:
|
|
158
|
+
kind: directory
|
|
159
|
+
disposition: retain
|
|
160
|
+
status: auditing
|
|
161
|
+
entryPoints:
|
|
162
|
+
- app-installations/index.ts
|
|
163
|
+
notes: >-
|
|
164
|
+
Composition-only service over a table that is still in development; preserve the
|
|
165
|
+
availability check that keeps it dormant wherever the table does not exist.
|
|
166
|
+
contracts:
|
|
167
|
+
exports:
|
|
168
|
+
summary: >-
|
|
169
|
+
Named exports are init, the mutable service binding, isAvailable and
|
|
170
|
+
actingContext. The service binding is undefined before init. The Express guard
|
|
171
|
+
that asks isAvailable lives with the Admin API routes in server/web.
|
|
172
|
+
sources:
|
|
173
|
+
- core/server/services/app-installations/index.ts
|
|
174
|
+
tests:
|
|
175
|
+
- test/e2e-api/admin/app-installations.test.ts
|
|
176
|
+
construction:
|
|
177
|
+
summary: >-
|
|
178
|
+
Boot calls synchronous init after database setup. It constructs one
|
|
179
|
+
AppInstallationsService from knex, the Action model and a manifest-rules getter;
|
|
180
|
+
repeated init returns once the binding exists.
|
|
181
|
+
sources:
|
|
182
|
+
- core/server/services/app-installations/index.ts
|
|
183
|
+
- core/boot.js
|
|
184
|
+
tests: []
|
|
185
|
+
configuration:
|
|
186
|
+
summary: >-
|
|
187
|
+
The site and Admin URLs and the development flag are read on every install, not
|
|
188
|
+
captured at construction. Availability is read per request from config.
|
|
189
|
+
sources:
|
|
190
|
+
- core/server/services/app-installations/index.ts
|
|
191
|
+
- core/server/data/schema/in-development.ts
|
|
192
|
+
tests:
|
|
193
|
+
- test/e2e-api/admin/app-installations.test.ts
|
|
194
|
+
unverified:
|
|
195
|
+
- consumers
|
|
196
|
+
- startup
|
|
197
|
+
- lifetime
|
|
198
|
+
- resources
|
|
199
|
+
- failures
|
|
200
|
+
blockers: []
|
|
157
201
|
audience-feedback:
|
|
158
202
|
kind: directory
|
|
159
203
|
disposition: retain
|
|
@@ -754,9 +798,26 @@ services:
|
|
|
754
798
|
- core/server/services/email-service/email-service-wrapper.js
|
|
755
799
|
- core/boot.js
|
|
756
800
|
tests: []
|
|
801
|
+
startup:
|
|
802
|
+
summary: >-
|
|
803
|
+
Boot initializes the wrapper, registers its send-email handler and starts jobs
|
|
804
|
+
before readiness. Outside test environments, interrupted-send recovery runs after
|
|
805
|
+
the app is mounted and readiness is signalled. Recovery is awaited within the
|
|
806
|
+
background sequence, but pending recovery does not delay the boot return or HTTP
|
|
807
|
+
availability; later background initialization continues after it resolves.
|
|
808
|
+
Unit orchestration tests cover initialization order, the test-environment gate
|
|
809
|
+
and sequencing after recovery. Handler-registration tests cover send-email
|
|
810
|
+
routing and its queue; real-boot HTTP tests cover jobs readiness and pending recovery.
|
|
811
|
+
sources:
|
|
812
|
+
- core/boot.js
|
|
813
|
+
- core/server/services/email-service/email-service-wrapper.js
|
|
814
|
+
tests:
|
|
815
|
+
- test/unit/boot-email-analytics.test.ts
|
|
816
|
+
- test/unit/server/services/jobs-service/register-job-handlers.test.ts
|
|
817
|
+
- test/e2e-server/boot-readiness.isolated.test.ts
|
|
818
|
+
- test/e2e-server/boot-background.isolated.test.ts
|
|
757
819
|
unverified:
|
|
758
820
|
- consumers
|
|
759
|
-
- startup
|
|
760
821
|
- lifetime
|
|
761
822
|
- configuration
|
|
762
823
|
- resources
|
|
@@ -768,6 +829,10 @@ services:
|
|
|
768
829
|
- >-
|
|
769
830
|
Audit locale-listener disposal and retained batch-sender state across restart, plus
|
|
770
831
|
the final status write after tracked batch work.
|
|
832
|
+
- >-
|
|
833
|
+
The boot test controls interrupted-send recovery and subsequent external effects.
|
|
834
|
+
It does not characterize recovery work, background failure handling, recurring
|
|
835
|
+
scheduling or background completion during shutdown.
|
|
771
836
|
email-suppression-list:
|
|
772
837
|
kind: directory
|
|
773
838
|
disposition: retain
|
|
@@ -2777,17 +2842,31 @@ services:
|
|
|
2777
2842
|
- core/server/services/route-settings/dynamic-routing-service.js
|
|
2778
2843
|
- core/boot.js
|
|
2779
2844
|
tests: []
|
|
2845
|
+
startup:
|
|
2846
|
+
summary: >-
|
|
2847
|
+
Boot configures routing in backend-only and frontend-only modes. The isolated HTTP
|
|
2848
|
+
tests cover configured post/tag URLs without a frontend, routes upload refreshing
|
|
2849
|
+
URLs through the same Content API consumer, and frontend rendering with the
|
|
2850
|
+
configured canonical URL. These exercise FileStore and normal boot wiring.
|
|
2851
|
+
Unit orchestration tests separately assert that initDynamicRouting waits for
|
|
2852
|
+
route-settings start and propagates its original error in both frontend modes.
|
|
2853
|
+
sources:
|
|
2854
|
+
- core/boot.js
|
|
2855
|
+
- core/server/services/route-settings/dynamic-routing-service.js
|
|
2856
|
+
tests:
|
|
2857
|
+
- test/unit/boot-dynamic-routing.test.ts
|
|
2858
|
+
- test/e2e-server/boot-backend-only.isolated.test.ts
|
|
2859
|
+
- test/e2e-server/boot-frontend-only.isolated.test.ts
|
|
2780
2860
|
unverified:
|
|
2781
2861
|
- consumers
|
|
2782
|
-
- startup
|
|
2783
2862
|
- lifetime
|
|
2784
2863
|
- configuration
|
|
2785
2864
|
- resources
|
|
2786
2865
|
- failures
|
|
2787
2866
|
blockers:
|
|
2788
2867
|
- >-
|
|
2789
|
-
Characterize repeated configuration/start
|
|
2790
|
-
|
|
2868
|
+
Characterize repeated configuration/start, reload failure paths and adapter ownership;
|
|
2869
|
+
parser helpers are independently consumed before service startup.
|
|
2791
2870
|
settings:
|
|
2792
2871
|
kind: directory
|
|
2793
2872
|
disposition: retain
|
|
@@ -3595,12 +3674,19 @@ services:
|
|
|
3595
3674
|
tests: []
|
|
3596
3675
|
startup:
|
|
3597
3676
|
summary: >-
|
|
3598
|
-
initDynamicRouting gives the shared URL
|
|
3599
|
-
frontend,
|
|
3677
|
+
Unit orchestration tests assert that initDynamicRouting gives the shared URL
|
|
3678
|
+
service to RouterManager without a frontend, passes the same router and URL
|
|
3679
|
+
instances to route-settings start, and waits for it in both frontend modes.
|
|
3680
|
+
They also assert propagation of the original startup error. The HTTP tests cover
|
|
3681
|
+
configured post/tag URLs in backend-only mode, URLs after upload through the same
|
|
3682
|
+
Content API consumer, and canonical URLs rendered by the frontend.
|
|
3600
3683
|
sources:
|
|
3601
3684
|
- core/boot.js
|
|
3602
3685
|
- core/server/services/route-settings/dynamic-routing-service.js
|
|
3603
|
-
tests:
|
|
3686
|
+
tests:
|
|
3687
|
+
- test/unit/boot-dynamic-routing.test.ts
|
|
3688
|
+
- test/e2e-server/boot-backend-only.isolated.test.ts
|
|
3689
|
+
- test/e2e-server/boot-frontend-only.isolated.test.ts
|
|
3604
3690
|
unverified:
|
|
3605
3691
|
- consumers
|
|
3606
3692
|
- lifetime
|
|
@@ -246,14 +246,10 @@ class StatsService {
|
|
|
246
246
|
const request = deps.request || require('../../lib/request-external');
|
|
247
247
|
const settingsCache = deps.settingsCache || require('../../../shared/settings-cache');
|
|
248
248
|
|
|
249
|
-
const labs = deps.labs || require('../../../shared/labs');
|
|
250
249
|
const webAnalyticsEnabled = settingsCache.get('web_analytics_enabled');
|
|
251
250
|
const TinybirdServiceWrapper = require('../tinybird');
|
|
252
251
|
|
|
253
|
-
if (
|
|
254
|
-
webAnalyticsEnabled ||
|
|
255
|
-
(labs.isSet('automationsTinybirdSync') && config.get('tinybird:stats'))
|
|
256
|
-
) {
|
|
252
|
+
if (webAnalyticsEnabled || config.get('tinybird:stats')) {
|
|
257
253
|
TinybirdServiceWrapper.init();
|
|
258
254
|
}
|
|
259
255
|
|
|
@@ -8,15 +8,12 @@ const bson_objectid_1 = __importDefault(require("bson-objectid"));
|
|
|
8
8
|
const logging_1 = __importDefault(require("@tryghost/logging"));
|
|
9
9
|
const config_1 = __importDefault(require("../../../shared/config"));
|
|
10
10
|
// @ts-expect-error This module lacks type definitions.
|
|
11
|
-
const labs_1 = __importDefault(require("../../../shared/labs"));
|
|
12
|
-
// @ts-expect-error This module lacks type definitions.
|
|
13
11
|
const settings_cache_1 = __importDefault(require("../../../shared/settings-cache"));
|
|
14
12
|
const db_1 = require("../../data/db");
|
|
15
13
|
const tinybird_sync_service_1 = require("./tinybird-sync-service");
|
|
16
14
|
const service = (0, tinybird_sync_service_1.createTinybirdSyncService)({
|
|
17
15
|
config: config_1.default,
|
|
18
16
|
settingsCache: settings_cache_1.default,
|
|
19
|
-
labs: labs_1.default,
|
|
20
17
|
knex: db_1.knex,
|
|
21
18
|
logging: logging_1.default,
|
|
22
19
|
random: Math.random,
|
|
@@ -15,7 +15,7 @@ const BATCH_SIZE = 5000;
|
|
|
15
15
|
const MAX_PAYLOAD_BYTES = 9 * 1024 * 1024;
|
|
16
16
|
const MAX_PAYLOAD_MESSAGES = 1000;
|
|
17
17
|
const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
18
|
-
function createTinybirdSyncService({ config, settingsCache,
|
|
18
|
+
function createTinybirdSyncService({ config, settingsCache, knex, logging, random, now, fetch, createId, }) {
|
|
19
19
|
let scheduled = false;
|
|
20
20
|
const syncAll = async (ingest) => {
|
|
21
21
|
const results = await Promise.allSettled(sync_table_to_tinybird_1.AUTOMATION_SYNC_TARGETS.map(async (target) => {
|
|
@@ -39,9 +39,6 @@ function createTinybirdSyncService({ config, settingsCache, labs, knex, logging,
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
const sync = async () => {
|
|
42
|
-
if (!labs.isSet('automationsTinybirdSync')) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
42
|
const ingest = (0, get_ingest_config_1.getIngestConfig)({ config, settingsCache });
|
|
46
43
|
if (!ingest) {
|
|
47
44
|
return;
|
|
@@ -59,8 +56,7 @@ function createTinybirdSyncService({ config, settingsCache, labs, knex, logging,
|
|
|
59
56
|
return;
|
|
60
57
|
}
|
|
61
58
|
scheduled = true;
|
|
62
|
-
|
|
63
|
-
logging.info({ system: { event: 'tinybird.sync.started' } }, `[Tinybird sync] Started: sync ${isSyncEnabled ? 'enabled' : 'disabled'} by labs flag (but may change)`);
|
|
59
|
+
logging.info({ system: { event: 'tinybird.sync.started' } }, '[Tinybird sync] Started');
|
|
64
60
|
const at = (0, cron_1.randomFiveMinuteCron)(random);
|
|
65
61
|
logging.info(`[Background Job] ${tinybird_sync_job_1.default.type} scheduled at ${at}`);
|
|
66
62
|
try {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const errors = require('@tryghost/errors');
|
|
1
2
|
const express = require('../../../../../shared/express');
|
|
2
3
|
const api = require('../../../../api').endpoints;
|
|
3
4
|
const { http } = require('@tryghost/api-framework');
|
|
@@ -6,6 +7,7 @@ const apiMw = require('../../middleware');
|
|
|
6
7
|
const mw = require('./middleware');
|
|
7
8
|
const labs = require('../../../../../shared/labs');
|
|
8
9
|
const limits = require('../../../../services/limits');
|
|
10
|
+
const appInstallations = require('../../../../services/app-installations');
|
|
9
11
|
|
|
10
12
|
const shared = require('../../../shared');
|
|
11
13
|
|
|
@@ -232,6 +234,27 @@ module.exports = function apiRoutes() {
|
|
|
232
234
|
metafieldsRouter.put('/:namespace/:key', http(api.membersMetafields.edit));
|
|
233
235
|
metafieldsRouter.delete('/:namespace/:key', http(api.membersMetafields.destroy));
|
|
234
236
|
|
|
237
|
+
// ## Apps
|
|
238
|
+
// Everything about apps sits behind the private `apps` flag, asked once here so a route
|
|
239
|
+
// added below is guarded by being there. The table is still in development, so it only
|
|
240
|
+
// exists in development and testing databases: anywhere else the routes answer as if
|
|
241
|
+
// the flag were off, whatever the flag says.
|
|
242
|
+
//
|
|
243
|
+
// Authenticated as a route here rather than inside the mount, for the same reason as
|
|
244
|
+
// the members metafields router above.
|
|
245
|
+
const appsRouter = express.Router('admin api apps');
|
|
246
|
+
router.all(['/apps', '/apps/*'], mw.authAdminApi);
|
|
247
|
+
router.use('/apps', appsRouter);
|
|
248
|
+
appsRouter.use(labs.enabledMiddleware('apps'));
|
|
249
|
+
// Answers as if apps did not exist wherever the table does not.
|
|
250
|
+
appsRouter.use((req, res, next) => {
|
|
251
|
+
next(appInstallations.isAvailable() ? undefined : new errors.NotFoundError());
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
appsRouter.get('/installations', http(api.appInstallations.browse));
|
|
255
|
+
appsRouter.get('/installations/:id', http(api.appInstallations.read));
|
|
256
|
+
appsRouter.delete('/installations/:id', http(api.appInstallations.destroy));
|
|
257
|
+
|
|
235
258
|
router.get('/members/:id', mw.authAdminApi, http(api.members.read));
|
|
236
259
|
router.put('/members/:id', mw.authAdminApi, http(api.members.edit));
|
|
237
260
|
router.delete('/members/:id', mw.authAdminApi, http(api.members.destroy));
|
|
@@ -396,18 +419,8 @@ module.exports = function apiRoutes() {
|
|
|
396
419
|
router.post('/db/media/inline', mw.authAdminApi, http(api.db.inlineMedia));
|
|
397
420
|
|
|
398
421
|
// ## Exports
|
|
399
|
-
router.get(
|
|
400
|
-
|
|
401
|
-
mw.authAdminApi,
|
|
402
|
-
labs.enabledMiddleware('selfServeArchives'),
|
|
403
|
-
http(api.exports.download),
|
|
404
|
-
);
|
|
405
|
-
router.post(
|
|
406
|
-
'/exports',
|
|
407
|
-
mw.authAdminApi,
|
|
408
|
-
labs.enabledMiddleware('selfServeArchives'),
|
|
409
|
-
http(api.exports.add),
|
|
410
|
-
);
|
|
422
|
+
router.get('/exports/download', mw.authAdminApi, http(api.exports.download));
|
|
423
|
+
router.post('/exports', mw.authAdminApi, http(api.exports.add));
|
|
411
424
|
|
|
412
425
|
// ## Slack
|
|
413
426
|
router.post('/slack/test', mw.authAdminApi, http(api.slack.sendTest));
|
package/core/shared/labs.js
CHANGED
|
@@ -27,15 +27,16 @@ const messages = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// flags in this list always return `true`, allows quick global enable prior to full flag removal
|
|
30
|
-
// Admin no longer reads admin7Pill, postsListReact or
|
|
31
|
-
// for older Admin builds during independent deployments.
|
|
30
|
+
// Admin no longer reads admin7Pill, postsListReact, membersActivityReact or selfServeArchives;
|
|
31
|
+
// they stay enabled for older Admin builds during independent deployments.
|
|
32
32
|
const GA_FEATURES = [
|
|
33
33
|
'automationAnalytics',
|
|
34
|
+
'automationRunAnalytics',
|
|
34
35
|
'admin7Pill',
|
|
35
36
|
'globalSearchReact',
|
|
36
37
|
'postsListReact',
|
|
37
38
|
'membersActivityReact',
|
|
38
|
-
'
|
|
39
|
+
'selfServeArchives',
|
|
39
40
|
];
|
|
40
41
|
|
|
41
42
|
// These features are considered publicly available and can be enabled/disabled by users
|
|
@@ -51,8 +52,6 @@ const PUBLIC_BETA_FEATURES = [
|
|
|
51
52
|
const PRIVATE_FEATURES = [
|
|
52
53
|
'automations',
|
|
53
54
|
'automationsPerTier',
|
|
54
|
-
'automationRunAnalytics',
|
|
55
|
-
'automationsTinybirdSync',
|
|
56
55
|
'stripeAutomaticTax',
|
|
57
56
|
'importMemberTier',
|
|
58
57
|
'csvContentImporter',
|
|
@@ -64,7 +63,6 @@ const PRIVATE_FEATURES = [
|
|
|
64
63
|
'membersCustomFields',
|
|
65
64
|
'stripeCheckoutCollection',
|
|
66
65
|
'paywallImprovements',
|
|
67
|
-
'selfServeArchives',
|
|
68
66
|
'machinePayments',
|
|
69
67
|
'editorReact',
|
|
70
68
|
'authReact',
|