ghost 6.56.0 → 6.57.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/bin/validate-adapters.js +74 -0
- package/bin/validate-adapters.ts +87 -0
- package/bin/warm-compile-cache.sh +60 -0
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
- package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
- package/core/app.js +1 -1
- package/core/boot.js +21 -25
- package/core/bridge.js +10 -12
- package/core/built/admin/assets/Combination-BDvCdg1t.js +41 -0
- package/core/built/admin/assets/about-C7O-AgRZ.js +1 -0
- package/core/built/admin/assets/{account-migration-D3O_yVck.js → account-migration-Cv_ol1ko.js} +1 -1
- package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-D8FbP-Z-.js → account-migration-DHFbePl6.js} +7 -7
- package/core/built/admin/assets/activitypub/activitypub.js +3 -3
- package/core/built/admin/assets/activitypub/{avatar-11EA1VSs.js → avatar-CT4tS2_S.js} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-jfbLG-xe.js → avatar-flipboard-ByYWKipo.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-f1jTQe2P.js → bluesky-sharing-srVnSHDz.js} +8 -8
- package/core/built/admin/assets/activitypub/{content-formatters-nmTOPY9M.js → content-formatters-C2sos5Ht.js} +3 -3
- package/core/built/admin/assets/activitypub/{copy-BCwo9ep3.js → copy-B1UBTOoZ.js} +2 -2
- package/core/built/admin/assets/activitypub/{dist-BEfX-0aW.js → dist-MPsb2WZ1.js} +4 -4
- package/core/built/admin/assets/activitypub/{domain-AaDlZlrA.js → domain-DzcM9Dzo.js} +9 -9
- package/core/built/admin/assets/activitypub/{edit-profile-DPi8Tl5M.js → edit-profile-CsQ_4xAk.js} +5 -5
- package/core/built/admin/assets/activitypub/{explore-CxTzjILQ.js → explore-CJe0DZBK.js} +8 -8
- package/core/built/admin/assets/activitypub/{feed-DPv5x2MX.js → feed-CKV3qWON.js} +9 -9
- package/core/built/admin/assets/activitypub/{field-DbgA8lat.js → field-DckHfDJf.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-BWuZoYkA.js → hash-Dlp8RM55.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-35VDQ2K2.js → inbox-CsPy4R0M.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DSrF_i1O.js → inbox-DAsjzUUe.js} +10 -10
- package/core/built/admin/assets/activitypub/{label-CPjFBbo7.js → label-DWyo9E5T.js} +3 -3
- package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-BkDC1XVZ.js → moderation-B0Inc03M.js} +7 -7
- package/core/built/admin/assets/activitypub/{note-xEyH4xT-.js → note-CSE54lER.js} +7 -7
- package/core/built/admin/assets/activitypub/{notifications-DClaL3ui.js → notifications-RSwbKqdh.js} +9 -9
- package/core/built/admin/assets/activitypub/{preferences-DpnOa-4Z.js → preferences-CE231n2_.js} +11 -11
- package/core/built/admin/assets/activitypub/{profile-Dvi9mz-6.js → profile-aaQ1j1Eo.js} +13 -13
- package/core/built/admin/assets/activitypub/{reply-Cs9ySd6Z.js → reply-Cjb0Vs3m.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-DFBeTnfn.js → routes-Djb0mc9k.js} +26 -26
- package/core/built/admin/assets/activitypub/{separator-C5u_VjzY.js → separator-D8pROI5n.js} +3 -3
- package/core/built/admin/assets/activitypub/{settings-CV2fFu6D.js → settings-DMc32IQl.js} +5 -5
- package/core/built/admin/assets/activitypub/{site-_TgDd_EM.js → site-DVIcsohX.js} +4 -4
- package/core/built/admin/assets/activitypub/{step-1-BfnNL2oF.js → step-1-9Yey9usF.js} +5 -5
- package/core/built/admin/assets/activitypub/{step-2-DLndZkO-.js → step-2-DdYmKRXV.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-A-PZTtRE.js → step-3-hdSkcjZU.js} +10 -10
- package/core/built/admin/assets/activitypub/{tabs-BUbz7tea.js → tabs-CUqseYzQ.js} +6 -6
- package/core/built/admin/assets/activitypub/{topic-filter-BdX_xYYT.js → topic-filter-Coni6OXM.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-EwuRemHh.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-cy4ACBc4.js → use-navigate-with-base-path-B046AM38.js} +24 -7
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-CyFcGfan.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
- package/core/built/admin/assets/activitypub/{x-7zk8mV2p.js → x-BRfRYd16.js} +2 -2
- package/core/built/admin/assets/add-integration-modal-m9Dv6f77.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-CPSVZL2H.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-Ci9Z5yat.js +1 -0
- package/core/built/admin/assets/analytics-DqSCVV8U.js +1 -0
- package/core/built/admin/assets/announcement-bar-modal-DU3avV7d.js +1 -0
- package/core/built/admin/assets/api-keys-BK0LylyK.js +1 -0
- package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
- package/core/built/admin/assets/{audience-CkBb2LlO.js → audience-DTJtXXWY.js} +1 -1
- package/core/built/admin/assets/automated-emails-PayPCT8L.js +1 -0
- package/core/built/admin/assets/{automations-DhKeS_U5.js → automations-162q5HjL.js} +1 -1
- package/core/built/admin/assets/{automations-cMllU545.js → automations-dtQ_MRZ7.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-DPGpyD-G.js → bluesky-sharing-B8Es3AuV.js} +1 -1
- package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
- package/core/built/admin/assets/{breadcrumb-DIfcF5ow.js → breadcrumb-tg5zL--z.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-DV0FkdcD.js → chart-helpers-CZElTGYL.js} +1 -1
- package/core/built/admin/assets/checkbox-Cyo-x7mC.js +1 -0
- package/core/built/admin/assets/{chunk.524.87b52f4fada529df2c4c.js → chunk.524.cd5498dd4e195f679266.js} +4 -4
- package/core/built/admin/assets/{chunk.582.340bcca873e806cbd4bb.js → chunk.582.6f92ec1f03734bf2ea3f.js} +6 -6
- package/core/built/admin/assets/{chunk.332.05991a101d67f732dec3.js → chunk.873.b8fc05a8ab877fb819ed.js} +28 -27
- package/core/built/admin/assets/code-editor-view-Dq1rQ4tS.js +1 -0
- package/core/built/admin/assets/color-picker-field-BonBgxhU.js +1 -0
- package/core/built/admin/assets/color-utils-BsrwXuDz.js +1 -0
- package/core/built/admin/assets/combobox-uX5r_paI.js +1 -0
- package/core/built/admin/assets/command-DxwBO2pF.js +1 -0
- package/core/built/admin/assets/comments-Dn-RO-nU.js +1 -0
- package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
- package/core/built/admin/assets/confirmation-modal-B6D6mk6R.js +1 -0
- package/core/built/admin/assets/content-api-modal-BxQ-7T6u.js +1 -0
- package/core/built/admin/assets/copy-field-BAxL4Zqn.js +1 -0
- package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
- package/core/built/admin/assets/custom-integration-modal---d_l7lc.js +1 -0
- package/core/built/admin/assets/{date-range-select-dXc-GsVe.js → date-range-select-PB2n1HZf.js} +1 -1
- package/core/built/admin/assets/debounce-Dg2fW5Rg.js +3 -0
- package/core/built/admin/assets/design-and-theme-modal-DYRT207a.js +23 -0
- package/core/built/admin/assets/dirty-confirm-dialog-BnDJ39ao.js +1 -0
- package/core/built/admin/assets/disabled-sources-indicator-iMHrkyV3.js +1 -0
- package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
- package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-D5vhMdXf.js} +1 -1
- package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
- package/core/built/admin/assets/dist-WeOIvSYM.js +23 -0
- package/core/built/admin/assets/{dist-DyViJwIy.js → dist-cFzKLG7y.js} +3 -3
- package/core/built/admin/assets/dist-j1xxxnY8.js +1 -0
- package/core/built/admin/assets/dist-myQaY0ka.js +1 -0
- package/core/built/admin/assets/{domain-B5ogXUBa.js → domain-BR8FIdjD.js} +1 -1
- package/core/built/admin/assets/{dropzone-BnFG7dDP.js → dropzone-hNxmUvwD.js} +1 -1
- package/core/built/admin/assets/{edit-profile-B07jCZy9.js → edit-profile-CENrkY0l.js} +1 -1
- package/core/built/admin/assets/editor-B18D0lm8.js +8 -0
- package/core/built/admin/assets/embed-signup-form-modal-BsCYUMvQ.js +9 -0
- package/core/built/admin/assets/{explore-Ca1y8HW_.js → explore-BmrnljWw.js} +1 -1
- package/core/built/admin/assets/{feed-x4iFMBP4.js → feed-Dy4li6i8.js} +1 -1
- package/core/built/admin/assets/{field-ChCAabZg.js → field-Dh_lwk13.js} +1 -1
- package/core/built/admin/assets/{filter-sources-LdtH1e-T.js → filter-sources-D2zDuJH5.js} +1 -1
- package/core/built/admin/assets/filters-3JBQcNtJ.js +1 -0
- package/core/built/admin/assets/{filters-CjVKRP8U.js → filters-CFt_BDe4.js} +1 -1
- package/core/built/admin/assets/first-promoter-modal-D6Rjxugw.js +1 -0
- package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
- package/core/built/admin/assets/{ghost-7f01131115186d1af84ead76e79646dd.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +300 -298
- package/core/built/admin/assets/{gift-link-modal-Buqh2vXh.js → gift-link-modal-zMtlJhLQ.js} +1 -1
- package/core/built/admin/assets/{gift-links-CsUhefWG.js → gift-links-CVuqLhsZ.js} +1 -1
- package/core/built/admin/assets/global-data-provider-B949LDXJ.js +1 -0
- package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
- package/core/built/admin/assets/{growth-Dy2UOSay.js → growth-C38Xtp8D.js} +2 -2
- package/core/built/admin/assets/{growth-DajOBdN0.js → growth-CFws67_i.js} +1 -1
- package/core/built/admin/assets/history-modal-ILAOkbxH.js +1 -0
- package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
- package/core/built/admin/assets/html-field-9a9Id_2q.js +1 -0
- package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
- package/core/built/admin/assets/images-CrQKVc9Q.js +1 -0
- package/core/built/admin/assets/{inbox-BUOLDoji.js → inbox-BrSRzyvz.js} +13 -13
- package/core/built/admin/assets/index-BHy2DZX-.js +67 -0
- package/core/built/admin/assets/index-Ds2iUoF6.css +1 -0
- package/core/built/admin/assets/index.es-B9tvf-t4.js +1 -0
- package/core/built/admin/assets/{input-group-uSK3XT7b.js → input-group-6vEPvX9S.js} +1 -1
- package/core/built/admin/assets/integration-header-B4DyXV06.js +1 -0
- package/core/built/admin/assets/integrations-BT6_iJvu.js +1 -0
- package/core/built/admin/assets/invite-user-modal-5xDCEw90.js +1 -0
- package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
- package/core/built/admin/assets/koenig-editor-base-BWLEB9Vf.js +1 -0
- package/core/built/admin/assets/{koenig-lexical-BFc4yFed.js → koenig-lexical-Cj62r_y9.js} +1 -1
- package/core/built/admin/assets/{kpi-card-BN-E6WYX.js → kpi-card-DOUnkZcN.js} +1 -1
- package/core/built/admin/assets/{kpi-card-CCt-qeWD.js → kpi-card-IHDo8Q56.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-DlxTbTs4.js → kpi-helpers-CjUdECAK.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-BlcUttpn.js → kpi-tabs-X3DkOYbI.js} +1 -1
- package/core/built/admin/assets/{label-AVrxwmsz.js → label-Dq6QaOuS.js} +1 -1
- package/core/built/admin/assets/label-picker-8YlhS2U3.js +1 -0
- package/core/built/admin/assets/{labels-DAIqKed7.js → labels-DAPjKPy2.js} +1 -1
- package/core/built/admin/assets/layout-TMj4P-vv.js +1 -0
- package/core/built/admin/assets/limit-modal-BRPsit_B.js +1 -0
- package/core/built/admin/assets/{link-helpers-D9LsDOkG.js → link-helpers-Bzkk68GD.js} +1 -1
- package/core/built/admin/assets/member-custom-fields-CmvsuuV_.js +1 -0
- package/core/built/admin/assets/member-detail-DCvRrkmJ.js +1 -0
- package/core/built/admin/assets/members-DxbSKngD.js +11 -0
- package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
- package/core/built/admin/assets/{moderation-CQ3urTFE.js → moderation-Da5lhhWh.js} +1 -1
- package/core/built/admin/assets/multi-select-combobox-CYdkH7KQ.js +1 -0
- package/core/built/admin/assets/navigation-modal-BIoTwtMy.js +1 -0
- package/core/built/admin/assets/newsletter-BptlVoJm.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-BIvxqE0N.js +1 -0
- package/core/built/admin/assets/newsletter-emails-Bf8IZLsP.js +1 -0
- package/core/built/admin/assets/{newsletters-vt3Ni1Ov.js → newsletters-BgrIZk0V.js} +1 -1
- package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
- package/core/built/admin/assets/{note-Dk877BTv.js → note-DYcLOmDb.js} +1 -1
- package/core/built/admin/assets/{notifications-CPlLjPxO.js → notifications-B2SPFIV9.js} +1 -1
- package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
- package/core/built/admin/assets/offers-container-modal-BZnZITMw.js +1 -0
- package/core/built/admin/assets/{onboarding-route-DdStOtjy.js → onboarding-route-DgdfxkQh.js} +1 -1
- package/core/built/admin/assets/overview-BYQJXSog.js +1 -0
- package/core/built/admin/assets/overview-Dt2_mR6U.js +1 -0
- package/core/built/admin/assets/{page-header-pp4dWmOS.js → page-header-CpmxJj5B.js} +1 -1
- package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
- package/core/built/admin/assets/pintura-modal-B4MuBuuO.js +1 -0
- package/core/built/admin/assets/portal-frame-BF0NBIji.js +1 -0
- package/core/built/admin/assets/portal-modal-DMVbm0oy.js +1 -0
- package/core/built/admin/assets/{post-analytics-BMJrI81R.js → post-analytics-CXWBOGyP.js} +1 -1
- package/core/built/admin/assets/post-analytics-header-ChzjXVPt.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-Btml5vJI.js → post-analytics-provider-Cgw4U_fw.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-AcUZkbfL.js → post-share-modal-B9Vmk_XM.js} +1 -1
- package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
- package/core/built/admin/assets/preferences-BfzJmnsd.js +5 -0
- package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
- package/core/built/admin/assets/preview-modal-Dw_HtD6S.js +1 -0
- package/core/built/admin/assets/profile-BT5vtv9b.js +1 -0
- package/core/built/admin/assets/radio-group-I7ou7PoJ.js +1 -0
- package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
- package/core/built/admin/assets/{router-provider-BmEZf6c6.js → router-provider-DSQfprxH.js} +1 -1
- package/core/built/admin/assets/select-D9oU1IoM.js +1 -0
- package/core/built/admin/assets/settings-CfZFbyu_.js +4 -0
- package/core/built/admin/assets/{settings-BGgk7oCI.js → settings-CpvHWix7.js} +1 -1
- package/core/built/admin/assets/settings-breadcrumbs-K0USP2nw.js +1 -0
- package/core/built/admin/assets/{share-modal-BeuydaBf.js → share-modal-ClIyPzc5.js} +1 -1
- package/core/built/admin/assets/slack-modal-1XXqOfiw.js +1 -0
- package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
- package/core/built/admin/assets/{sort-button-Df4t2AL8.js → sort-button-CdBsm40v.js} +1 -1
- package/core/built/admin/assets/{source-icon-ajoaKb41.js → source-icon-CEYmVLXR.js} +1 -1
- package/core/built/admin/assets/{stats-C_lfxZAw.js → stats-D8nZCTy5.js} +1 -1
- package/core/built/admin/assets/{stats-view-Cxot1iD8.js → stats-view-CugFVf0g.js} +1 -1
- package/core/built/admin/assets/{step-1-ogAA4lDr.js → step-1-DOYjrORn.js} +1 -1
- package/core/built/admin/assets/{step-2-C5Xini-7.js → step-2-vA6RsarH.js} +1 -1
- package/core/built/admin/assets/{step-3-CZv1FV81.js → step-3-BtIBxMId.js} +2 -2
- package/core/built/admin/assets/stripe-button-UFP4QRXd.js +1 -0
- package/core/built/admin/assets/stripe-connect-modal-C_L3uM6n.js +1 -0
- package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
- package/core/built/admin/assets/{table-DFj1wiYv.js → table-TMCgCQK7.js} +1 -1
- package/core/built/admin/assets/tabs-Dd6oOKbv.js +1 -0
- package/core/built/admin/assets/tag-detail-D3l-UJPR.js +1 -0
- package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
- package/core/built/admin/assets/tags-WdKQGWR8.js +1 -0
- package/core/built/admin/assets/testimonials-modal-CqWp-xXJ.js +1 -0
- package/core/built/admin/assets/tier-detail-modal-BkcR2u0A.js +1 -0
- package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
- package/core/built/admin/assets/toggle-group-Guorw93z.js +1 -0
- package/core/built/admin/assets/{topic-filter-DGDxDalo.js → topic-filter-CbgoWIHn.js} +1 -1
- package/core/built/admin/assets/transistor-modal-DuNnnpal.js +1 -0
- package/core/built/admin/assets/unsplash-modal-y2bVwTHH.js +1 -0
- package/core/built/admin/assets/{use-analytics-data-C76v4LIp.js → use-analytics-data-B-b4wNSC.js} +1 -1
- package/core/built/admin/assets/use-check-theme-limit-error-m09J38Mq.js +2 -0
- package/core/built/admin/assets/use-feature-flag-ud0Qz8vt.js +1 -0
- package/core/built/admin/assets/use-filter-params-BnbBDc7T.js +1 -0
- package/core/built/admin/assets/use-filterable-api-iMpDZBxD.js +1 -0
- package/core/built/admin/assets/use-form-vRjI0Kwk.js +1 -0
- package/core/built/admin/assets/{use-growth-stats-aNXJOmQr.js → use-growth-stats-DPU5rt3W.js} +1 -1
- package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
- package/core/built/admin/assets/use-limiter-BkN3E4gw.js +2 -0
- package/core/built/admin/assets/use-pintura-config-C_z8HTG2.js +1 -0
- package/core/built/admin/assets/use-pintura-editor-DNgbkhGp.js +1 -0
- package/core/built/admin/assets/{use-post-referrers-CG0SZioj.js → use-post-referrers-C2mXyrp-.js} +1 -1
- package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
- package/core/built/admin/assets/{use-reply-chain-data-RLdPKRmS.js → use-reply-chain-data-DaK8q2qQ.js} +1 -1
- package/core/built/admin/assets/use-setting-group-YlYcoNyP.js +1 -0
- package/core/built/admin/assets/use-settings-navigation-Bf1g1rEh.js +1 -0
- package/core/built/admin/assets/{use-simple-pagination-DgcF69KI.js → use-simple-pagination-D9ckKo5-.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-C1R_Kbmy.js → use-tinybird-query-GSa32hVw.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-C9fPjVM_.js → use-tinybird-token-CKLOZFsw.js} +1 -1
- package/core/built/admin/assets/use-url-input-z-p0Kewm.js +5 -0
- package/core/built/admin/assets/user-detail-modal-BMxOA4Sv.js +1 -0
- package/core/built/admin/assets/{users-Dkv8NQZe.js → users-B6hIds6n.js} +1 -1
- package/core/built/admin/assets/validator-Cr0sMKuW.js +7 -0
- package/core/built/admin/assets/{virtual-list-BmTUzeoF.js → virtual-list-RHeEL05L.js} +1 -1
- package/core/built/admin/assets/web-Bw2QMO5u.js +2 -0
- package/core/built/admin/assets/web-xJtcwMy4.js +1 -0
- package/core/built/admin/assets/zapier-modal-BP2f4-vY.js +1 -0
- package/core/built/admin/index.html +15 -13
- package/core/frontend/helpers/authors.js +1 -1
- package/core/frontend/helpers/link.js +3 -8
- package/core/frontend/helpers/tags.js +1 -1
- package/core/frontend/meta/author-url.js +2 -2
- package/core/frontend/meta/url.js +4 -4
- package/core/frontend/services/proxy.js +1 -1
- package/core/frontend/services/routing/events.js +20 -6
- package/core/frontend/services/routing/permalink-adapter.js +0 -13
- package/core/frontend/services/routing/permalink-adapter.ts +0 -13
- package/core/frontend/services/routing/router-manager.js +15 -9
- package/core/frontend/services/sitemap/base-site-map-generator.js +1 -28
- package/core/frontend/services/sitemap/site-map-manager.js +22 -57
- package/core/server/adapters/route-settings/utils.js +10 -2
- package/core/server/adapters/route-settings/utils.ts +12 -2
- package/core/server/api/endpoints/email-post.js +1 -1
- package/core/server/api/endpoints/previews.js +1 -1
- package/core/server/api/endpoints/search-index-public.js +3 -3
- package/core/server/api/endpoints/search-index.js +4 -4
- package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/comments.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/pages.js +3 -3
- package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
- package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/users.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/utils/url.js +31 -13
- package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +2 -2
- package/core/server/api/endpoints/utils/serializers/output/mappers/tags.js +2 -2
- package/core/server/api/endpoints/utils/serializers/output/mappers/users.js +8 -0
- package/core/server/api/endpoints/utils/serializers/output/utils/url.js +18 -11
- package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +74 -0
- package/core/server/data/migrations/versions/6.57/2026-08-04-18-54-14-reset-automation-email-analytics.js +25 -0
- package/core/server/data/schema/commands.js +21 -5
- package/core/server/data/schema/schema.js +24 -5
- package/core/server/lib/common/to-plain.ts +1 -1
- package/core/server/models/post.js +2 -2
- package/core/server/services/adapter-manager/adapter-manager.js +1 -45
- package/core/server/services/adapter-manager/adapter-manager.ts +2 -48
- package/core/server/services/adapter-manager/adapter-paths.js +24 -0
- package/core/server/services/adapter-manager/adapter-paths.ts +21 -0
- package/core/server/services/adapter-manager/base-classes.js +22 -0
- package/core/server/services/adapter-manager/base-classes.ts +22 -0
- package/core/server/services/adapter-manager/index.js +4 -27
- package/core/server/services/adapter-manager/index.ts +4 -31
- package/core/server/services/adapter-manager/utils.js +50 -0
- package/core/server/services/adapter-manager/utils.ts +57 -0
- package/core/server/services/audience-feedback/audience-feedback-controller.js +1 -1
- package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
- package/core/server/services/automations/service.js +2 -2
- package/core/server/services/automations/service.ts +2 -2
- package/core/server/services/comments/comments-service-emails.js +1 -1
- package/core/server/services/comments/comments-service.js +1 -1
- package/core/server/services/email-analytics/email-analytics-service-wrapper.js +11 -2
- package/core/server/services/email-analytics/email-analytics-service.js +1 -1
- package/core/server/services/email-service/email-service-wrapper.js +2 -1
- package/core/server/services/email-service/mailgun-email-provider.js +15 -0
- package/core/server/services/indexnow-ping/indexnow-ping-service.js +1 -1
- package/core/server/services/indexnow-ping/indexnow-ping-service.ts +2 -4
- package/core/server/services/lib/mailgun-client.js +3 -3
- package/core/server/services/media-inliner/external-media-inliner.js +12 -8
- package/core/server/services/member-attribution/url-translator.js +5 -7
- package/core/server/services/member-welcome-emails/service.js +72 -12
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/import-export/import/completion-email.js +8 -4
- package/core/server/services/members/import-export/import/completion-email.ts +9 -4
- package/core/server/services/members/import-export/import/importer.js +25 -4
- package/core/server/services/members/import-export/import/importer.ts +26 -6
- package/core/server/services/members/import-export/import/row.ts +5 -1
- package/core/server/services/members/import-export/index.js +1 -5
- package/core/server/services/members/import-export/index.ts +2 -6
- package/core/server/services/members-custom-fields/queries.js +4 -13
- package/core/server/services/members-custom-fields/queries.ts +4 -13
- package/core/server/services/members-custom-fields/schema.js +18 -27
- package/core/server/services/members-custom-fields/schema.ts +17 -27
- package/core/server/services/members-custom-fields/storage.js +94 -27
- package/core/server/services/members-custom-fields/storage.ts +114 -39
- package/core/server/services/members-custom-fields/values-service.js +98 -219
- package/core/server/services/members-custom-fields/values-service.ts +110 -238
- package/core/server/services/mentions/resource-service.js +1 -1
- package/core/server/services/mentions/service.js +2 -2
- package/core/server/services/route-settings/dynamic-routing-service.js +9 -59
- package/core/server/services/slack-ping/slack-ping-service.js +2 -2
- package/core/server/services/slack-ping/slack-ping-service.ts +3 -5
- package/core/server/services/stats/content-stats-service.js +3 -3
- package/core/server/services/stats/posts-stats-service.js +2 -2
- package/core/server/services/url/config.js +90 -135
- package/core/server/services/url/index.js +6 -49
- package/core/server/services/url/lazy-find-resource.js +14 -11
- package/core/server/services/url/lazy-find-resource.ts +14 -11
- package/core/server/services/url/lazy-url-service.js +174 -94
- package/core/server/services/url/lazy-url-service.ts +233 -97
- package/core/server/services/url/routable-resources.js +62 -71
- package/core/server/services/url/router-filter.js +12 -4
- package/core/server/services/url/router-filter.ts +12 -4
- package/core/server/services/webhooks/index.js +1 -1
- package/core/server/services/webhooks/serialize.js +2 -1
- package/core/shared/config/env/config.testing-mysql.json +1 -2
- package/core/shared/config/env/config.testing.json +1 -2
- package/core/shared/events/index.js +1 -2
- package/core/shared/labs.js +3 -3
- package/package.json +5 -6
- package/pnpm-lock.yaml +6 -9
- package/pnpm-workspace.yaml +2 -2
- package/tsconfig.json +1 -0
- package/core/built/admin/assets/Combination-BCm9FL4o.js +0 -41
- package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +0 -1
- package/core/built/admin/assets/automated-emails-6t2bIRWf.js +0 -7
- package/core/built/admin/assets/checkbox-BIl077a0.js +0 -1
- package/core/built/admin/assets/code-editor-view-C0aWNwdl.js +0 -1
- package/core/built/admin/assets/comments-qwhIjH3c.js +0 -1
- package/core/built/admin/assets/disabled-sources-indicator-DYAXW-6V.js +0 -1
- package/core/built/admin/assets/dist-BUKmD51u.js +0 -1
- package/core/built/admin/assets/dist-CNlMj7ML.js +0 -1
- package/core/built/admin/assets/dist-DDY6GRVz.js +0 -1
- package/core/built/admin/assets/dist-DG78yroX.js +0 -23
- package/core/built/admin/assets/editor-DDYSOXGN.js +0 -8
- package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
- package/core/built/admin/assets/filters-CLehPo_D.js +0 -1
- package/core/built/admin/assets/index-08n05-rc.js +0 -67
- package/core/built/admin/assets/index-DgUzgFvS.css +0 -1
- package/core/built/admin/assets/label-picker-DVnpu_IF.js +0 -1
- package/core/built/admin/assets/layout-B0YLh7R9.js +0 -1
- package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +0 -1
- package/core/built/admin/assets/member-detail-CIVAdKXf.js +0 -1
- package/core/built/admin/assets/members-oLPiSkXD.js +0 -10
- package/core/built/admin/assets/modals-CB7v-k3T.js +0 -31
- package/core/built/admin/assets/newsletter-rbeDX9Wk.js +0 -1
- package/core/built/admin/assets/overview--4V-kx9c.js +0 -1
- package/core/built/admin/assets/overview-DSLv30oz.js +0 -1
- package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +0 -1
- package/core/built/admin/assets/preferences-Ru4CRPHC.js +0 -5
- package/core/built/admin/assets/profile-BNb1oj7D.js +0 -1
- package/core/built/admin/assets/select-B0HteHrp.js +0 -1
- package/core/built/admin/assets/settings-B1C9Q3wo.js +0 -4
- package/core/built/admin/assets/social-urls-DA2lXrhW.js +0 -8
- package/core/built/admin/assets/switch-CyQcwMAv.js +0 -1
- package/core/built/admin/assets/tabs-D4y1gqUd.js +0 -1
- package/core/built/admin/assets/tags-Dmft-Y4O.js +0 -1
- package/core/built/admin/assets/toggle-group-R2QMFvXW.js +0 -1
- package/core/built/admin/assets/use-filter-params-WrdhAISv.js +0 -1
- package/core/built/admin/assets/web-BqW0b0_C.js +0 -1
- package/core/built/admin/assets/web-Cy5GKMWM.js +0 -2
- package/core/server/services/url/local-file-cache.js +0 -75
- package/core/server/services/url/queue.js +0 -260
- package/core/server/services/url/resource.js +0 -102
- package/core/server/services/url/resources.js +0 -480
- package/core/server/services/url/url-generator.js +0 -276
- package/core/server/services/url/url-service-facade.js +0 -366
- package/core/server/services/url/url-service-facade.ts +0 -519
- package/core/server/services/url/url-service.js +0 -387
- package/core/server/services/url/urls.js +0 -140
- package/core/shared/events/url-resource-updated-event.js +0 -33
- /package/core/built/admin/assets/{dist-CqO2lWS0.js → dist-CqO2lWS02.js} +0 -0
|
@@ -2,6 +2,11 @@ const logging = require('@tryghost/logging');
|
|
|
2
2
|
const errors = require('@tryghost/errors');
|
|
3
3
|
const debug = require('@tryghost/debug')('email-service:mailgun-provider-service');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} Config
|
|
7
|
+
* @prop {(key: string) => unknown} get
|
|
8
|
+
*/
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* @typedef {object} Recipient
|
|
7
12
|
* @prop {string} email
|
|
@@ -29,18 +34,22 @@ const debug = require('@tryghost/debug')('email-service:mailgun-provider-service
|
|
|
29
34
|
|
|
30
35
|
class MailgunEmailProvider {
|
|
31
36
|
#mailgunClient;
|
|
37
|
+
#config;
|
|
32
38
|
#errorHandler;
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* @param {object} dependencies
|
|
36
42
|
* @param {import('../lib/mailgun-client')} dependencies.mailgunClient - mailgun client to send emails
|
|
43
|
+
* @param {Config} dependencies.config
|
|
37
44
|
* @param {Function} [dependencies.errorHandler] - custom error handler for logging exceptions
|
|
38
45
|
*/
|
|
39
46
|
constructor({
|
|
40
47
|
mailgunClient,
|
|
48
|
+
config,
|
|
41
49
|
errorHandler
|
|
42
50
|
}) {
|
|
43
51
|
this.#mailgunClient = mailgunClient;
|
|
52
|
+
this.#config = config;
|
|
44
53
|
this.#errorHandler = errorHandler;
|
|
45
54
|
}
|
|
46
55
|
|
|
@@ -108,12 +117,18 @@ class MailgunEmailProvider {
|
|
|
108
117
|
plaintext,
|
|
109
118
|
from,
|
|
110
119
|
replyTo,
|
|
120
|
+
tags: ['bulk-email'],
|
|
111
121
|
domainOverride,
|
|
112
122
|
id: emailId,
|
|
113
123
|
track_opens: !!options.openTrackingEnabled,
|
|
114
124
|
track_clicks: !!options.clickTrackingEnabled
|
|
115
125
|
};
|
|
116
126
|
|
|
127
|
+
const mailgunTagFromConfig = this.#config.get('bulkEmail:mailgun:tag');
|
|
128
|
+
if (typeof mailgunTagFromConfig === 'string' && mailgunTagFromConfig.length > 0) {
|
|
129
|
+
messageData.tags.push(mailgunTagFromConfig);
|
|
130
|
+
}
|
|
131
|
+
|
|
117
132
|
if (options.deliveryTime && options.deliveryTime instanceof Date) {
|
|
118
133
|
messageData.deliveryTime = options.deliveryTime;
|
|
119
134
|
}
|
|
@@ -94,7 +94,7 @@ class IndexNowPingService {
|
|
|
94
94
|
// publishing.
|
|
95
95
|
let url = null;
|
|
96
96
|
try {
|
|
97
|
-
url = this.urlService.
|
|
97
|
+
url = this.urlService.getUrlForResource({ ...post, type: 'posts' }, { absolute: true });
|
|
98
98
|
if (!url || url.endsWith('/404/')) {
|
|
99
99
|
this.logging.warn({
|
|
100
100
|
event: { name: 'indexnow.unresolved_url' },
|
|
@@ -44,9 +44,7 @@ type Labs = {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
type UrlService = {
|
|
47
|
-
|
|
48
|
-
getUrlForResource(resource: Record<string, unknown>, options: {absolute: boolean}): string | null;
|
|
49
|
-
};
|
|
47
|
+
getUrlForResource(resource: Record<string, unknown>, options: {absolute: boolean}): string | null;
|
|
50
48
|
};
|
|
51
49
|
|
|
52
50
|
type UrlUtils = {
|
|
@@ -170,7 +168,7 @@ export class IndexNowPingService {
|
|
|
170
168
|
// publishing.
|
|
171
169
|
let url: string | null = null;
|
|
172
170
|
try {
|
|
173
|
-
url = this.urlService.
|
|
171
|
+
url = this.urlService.getUrlForResource({...post, type: 'posts'}, {absolute: true});
|
|
174
172
|
|
|
175
173
|
if (!url || url.endsWith('/404/')) {
|
|
176
174
|
this.logging.warn({
|
|
@@ -85,9 +85,9 @@ module.exports = class MailgunClient {
|
|
|
85
85
|
messageData['v:email-id'] = message.id;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const tags = ['
|
|
89
|
-
if (
|
|
90
|
-
tags.push(
|
|
88
|
+
const tags = ['ghost-email'];
|
|
89
|
+
if (Array.isArray(message.tags)) {
|
|
90
|
+
tags.push(...message.tags);
|
|
91
91
|
}
|
|
92
92
|
messageData['o:tag'] = tags;
|
|
93
93
|
|
|
@@ -5,7 +5,6 @@ const errors = require('@tryghost/errors');
|
|
|
5
5
|
const logging = require('@tryghost/logging');
|
|
6
6
|
const string = require('@tryghost/string');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const convert = require('heic-convert');
|
|
9
8
|
|
|
10
9
|
let fileTypeFromBuffer;
|
|
11
10
|
|
|
@@ -102,20 +101,25 @@ class ExternalMediaInliner {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
// If the file is heic or heif, attempt to convert it to jpeg
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if (extension === 'heic' || extension === 'heif') {
|
|
105
|
+
// Lazy: pulls in libheif-js, a WASM codec costing ~50ms to load at boot.
|
|
106
|
+
// Deliberately outside the try — a missing codec is not a conversion error,
|
|
107
|
+
// and swallowing it would store an unconvertible .heic in its place.
|
|
108
|
+
const convert = require('heic-convert');
|
|
109
|
+
|
|
110
|
+
try {
|
|
107
111
|
body = await convert({
|
|
108
112
|
buffer: body,
|
|
109
113
|
format: 'JPEG'
|
|
110
114
|
});
|
|
111
115
|
|
|
112
116
|
extension = 'jpg';
|
|
117
|
+
} catch (error) {
|
|
118
|
+
logging.error(`Error converting file to JPEG: ${requestURL}`);
|
|
119
|
+
logging.error(new errors.DataImportError({
|
|
120
|
+
err: error
|
|
121
|
+
}));
|
|
113
122
|
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
logging.error(`Error converting file to JPEG: ${requestURL}`);
|
|
116
|
-
logging.error(new errors.DataImportError({
|
|
117
|
-
err: error
|
|
118
|
-
}));
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
const removeExtRegExp = new RegExp(`.${extension}`, '');
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {Object} UrlService
|
|
3
|
-
* @prop {
|
|
4
|
-
*
|
|
5
|
-
* resolveUrl: (path: string) => Promise<Object | null>
|
|
6
|
-
* }} facade
|
|
3
|
+
* @prop {(resource: Object, options: Object) => string} getUrlForResource
|
|
4
|
+
* @prop {(path: string) => Promise<Object | null>} resolveUrl
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
7
|
const {toPlain} = require('../../lib/common/to-plain');
|
|
@@ -106,7 +104,7 @@ class UrlTranslator {
|
|
|
106
104
|
// surface as an error.
|
|
107
105
|
let resource;
|
|
108
106
|
try {
|
|
109
|
-
resource = await this.urlService.
|
|
107
|
+
resource = await this.urlService.resolveUrl(path);
|
|
110
108
|
} catch (err) {
|
|
111
109
|
return;
|
|
112
110
|
}
|
|
@@ -155,10 +153,10 @@ class UrlTranslator {
|
|
|
155
153
|
}
|
|
156
154
|
// Lazy URL service evaluates permalink templates against resource fields
|
|
157
155
|
// (slug, published_at, primary_tag, ...). Caller already loaded the model,
|
|
158
|
-
// so spread its plain data so those fields reach the
|
|
156
|
+
// so spread its plain data so those fields reach the URL service.
|
|
159
157
|
const data = toPlain(model);
|
|
160
158
|
const resource = {...data, id, type: TYPE_TO_RESOURCE[type]};
|
|
161
|
-
return this.urlService.
|
|
159
|
+
return this.urlService.getUrlForResource(resource, {absolute});
|
|
162
160
|
}
|
|
163
161
|
|
|
164
162
|
async getResourceById(id, type) {
|
|
@@ -9,6 +9,8 @@ const emailAddressService = require('../email-address');
|
|
|
9
9
|
const settingsHelpers = require('../settings-helpers');
|
|
10
10
|
const EmailAddressParser = require('../email-address/email-address-parser');
|
|
11
11
|
const mail = require('../mail');
|
|
12
|
+
const MailgunClient = require('../lib/mailgun-client');
|
|
13
|
+
const config = require('../../../shared/config');
|
|
12
14
|
const labs = require('../../../shared/labs');
|
|
13
15
|
const {Automation, EmailDesignSetting, Newsletter} = require('../../models');
|
|
14
16
|
const MemberWelcomeEmailRenderer = require('./member-welcome-email-renderer');
|
|
@@ -46,7 +48,8 @@ const getSenderDetails = (designSettingsJson) => {
|
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
class MemberWelcomeEmailService {
|
|
49
|
-
#
|
|
51
|
+
#transactionalMailer;
|
|
52
|
+
#bulkMailer;
|
|
50
53
|
#renderer;
|
|
51
54
|
#magicLinkService;
|
|
52
55
|
#memberWelcomeEmails = {free: null, paid: null};
|
|
@@ -54,7 +57,8 @@ class MemberWelcomeEmailService {
|
|
|
54
57
|
|
|
55
58
|
constructor({t, dir, singleUseTokenProvider}) {
|
|
56
59
|
emailAddressService.init();
|
|
57
|
-
this.#
|
|
60
|
+
this.#transactionalMailer = new mail.GhostMailer();
|
|
61
|
+
this.#bulkMailer = new MailgunClient({config, settings: settingsCache});
|
|
58
62
|
this.#renderer = new MemberWelcomeEmailRenderer({t, dir});
|
|
59
63
|
|
|
60
64
|
const getSigninURL = (token) => {
|
|
@@ -306,7 +310,7 @@ class MemberWelcomeEmailService {
|
|
|
306
310
|
logging.warn(message.text);
|
|
307
311
|
}
|
|
308
312
|
|
|
309
|
-
return this.#
|
|
313
|
+
return this.#transactionalMailer.send({
|
|
310
314
|
from: fromEmail,
|
|
311
315
|
subject: 'Verify email address',
|
|
312
316
|
forceTextContent: true,
|
|
@@ -409,18 +413,16 @@ class MemberWelcomeEmailService {
|
|
|
409
413
|
getSenderDetails(email.designSettings)
|
|
410
414
|
);
|
|
411
415
|
|
|
412
|
-
const headers = unsubscribe?.oneClickUrl ? {
|
|
413
|
-
'List-Unsubscribe': `<${unsubscribe.oneClickUrl}>`,
|
|
414
|
-
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
|
|
415
|
-
} : undefined;
|
|
416
|
-
|
|
417
416
|
/** @type {string[]} */ let tags;
|
|
417
|
+
let sendEmail;
|
|
418
418
|
switch (emailType) {
|
|
419
419
|
case 'welcome':
|
|
420
420
|
tags = [MEMBER_WELCOME_EMAIL_TAG];
|
|
421
|
+
sendEmail = this.#sendTransactionalEmail.bind(this);
|
|
421
422
|
break;
|
|
422
423
|
case 'automation':
|
|
423
424
|
tags = [AUTOMATION_EMAIL_TAG];
|
|
425
|
+
sendEmail = this.#sendBulkEmail.bind(this);
|
|
424
426
|
break;
|
|
425
427
|
default: {
|
|
426
428
|
/** @type {never} */ const _exhaustive = emailType;
|
|
@@ -430,19 +432,78 @@ class MemberWelcomeEmailService {
|
|
|
430
432
|
}
|
|
431
433
|
}
|
|
432
434
|
|
|
433
|
-
return await
|
|
435
|
+
return await sendEmail({
|
|
434
436
|
to: member.email,
|
|
435
437
|
subject,
|
|
436
438
|
html,
|
|
437
439
|
text,
|
|
440
|
+
tags,
|
|
441
|
+
trackOpens,
|
|
442
|
+
listUnsubscribe: unsubscribe?.oneClickUrl,
|
|
443
|
+
...senderOptions
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* @param {object} options
|
|
449
|
+
* @param {string} options.to
|
|
450
|
+
* @param {string} options.subject
|
|
451
|
+
* @param {string} options.html
|
|
452
|
+
* @param {string} options.text
|
|
453
|
+
* @param {string} options.from
|
|
454
|
+
* @param {string} [options.replyTo]
|
|
455
|
+
* @param {string[]} [options.tags]
|
|
456
|
+
* @param {boolean} [options.trackOpens]
|
|
457
|
+
* @param {string} [options.listUnsubscribe]
|
|
458
|
+
* @returns {Promise<unknown>}
|
|
459
|
+
*/
|
|
460
|
+
async #sendTransactionalEmail({to, subject, html, text, from, replyTo, tags, trackOpens, listUnsubscribe}) {
|
|
461
|
+
const headers = listUnsubscribe ? {
|
|
462
|
+
'List-Unsubscribe': `<${listUnsubscribe}>`,
|
|
463
|
+
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
|
|
464
|
+
} : undefined;
|
|
465
|
+
|
|
466
|
+
return await this.#transactionalMailer.send({
|
|
467
|
+
to,
|
|
468
|
+
subject,
|
|
469
|
+
html,
|
|
470
|
+
text,
|
|
438
471
|
forceTextContent: true,
|
|
439
472
|
tags,
|
|
440
473
|
...(headers ? {headers} : {}),
|
|
441
474
|
...(typeof trackOpens === 'boolean' ? {trackOpens} : {}),
|
|
442
|
-
|
|
475
|
+
from,
|
|
476
|
+
replyTo
|
|
443
477
|
});
|
|
444
478
|
}
|
|
445
479
|
|
|
480
|
+
/**
|
|
481
|
+
* @param {object} options
|
|
482
|
+
* @param {string} options.to
|
|
483
|
+
* @param {string} options.subject
|
|
484
|
+
* @param {string} options.html
|
|
485
|
+
* @param {string} options.text
|
|
486
|
+
* @param {string} options.from
|
|
487
|
+
* @param {string} [options.replyTo]
|
|
488
|
+
* @param {string[]} [options.tags]
|
|
489
|
+
* @param {boolean} [options.trackOpens]
|
|
490
|
+
* @param {string} [options.listUnsubscribe]
|
|
491
|
+
* @returns {Promise<unknown>}
|
|
492
|
+
*/
|
|
493
|
+
async #sendBulkEmail({to, subject, html, text, from, replyTo, tags, trackOpens, listUnsubscribe}) {
|
|
494
|
+
return await this.#bulkMailer.send({
|
|
495
|
+
subject,
|
|
496
|
+
html,
|
|
497
|
+
plaintext: text,
|
|
498
|
+
from,
|
|
499
|
+
replyTo,
|
|
500
|
+
tags,
|
|
501
|
+
...(typeof trackOpens === 'boolean' ? {track_opens: trackOpens} : {})
|
|
502
|
+
}, {
|
|
503
|
+
[to]: listUnsubscribe ? {list_unsubscribe: listUnsubscribe} : {}
|
|
504
|
+
}, []);
|
|
505
|
+
}
|
|
506
|
+
|
|
446
507
|
async send({member, memberStatus}) {
|
|
447
508
|
const email = this.#memberWelcomeEmails[memberStatus];
|
|
448
509
|
|
|
@@ -651,12 +712,11 @@ class MemberWelcomeEmailService {
|
|
|
651
712
|
getSenderDetails(designSettingsJson)
|
|
652
713
|
);
|
|
653
714
|
|
|
654
|
-
await this.#
|
|
715
|
+
await this.#sendTransactionalEmail({
|
|
655
716
|
to: email,
|
|
656
717
|
subject: `[Test] ${renderedSubject}`,
|
|
657
718
|
html,
|
|
658
719
|
text,
|
|
659
|
-
forceTextContent: true,
|
|
660
720
|
...senderOptions
|
|
661
721
|
});
|
|
662
722
|
}
|
|
@@ -54,7 +54,7 @@ function trimLeadingWhitespace(strings, ...values) {
|
|
|
54
54
|
|
|
55
55
|
function createApiInstance(config) {
|
|
56
56
|
const membersApiInstance = MembersApi({
|
|
57
|
-
urlService
|
|
57
|
+
urlService,
|
|
58
58
|
tokenConfig: config.getTokenConfig(),
|
|
59
59
|
auth: {
|
|
60
60
|
getSigninURL: config.getSigninURL.bind(config),
|
|
@@ -6,14 +6,18 @@ const csv_2 = require("@tryghost/custom-field-types/csv");
|
|
|
6
6
|
const emailTemplate = require('./email-template');
|
|
7
7
|
// Turn an ORM validation error into copy a member manager can act on. Presentation
|
|
8
8
|
// only: the API response keeps the raw messages, and just the emailed report is
|
|
9
|
-
// rewritten.
|
|
10
|
-
|
|
9
|
+
// rewritten. Takes one reason at a time, so a rewrite matching to the end of its input
|
|
10
|
+
// cannot consume the reason after it.
|
|
11
|
+
function humaniseReason(message) {
|
|
11
12
|
return message
|
|
12
13
|
.replace('Value in [members.email] cannot be blank.', 'Missing email address')
|
|
13
14
|
.replace('Value in [members.note] exceeds maximum length of 2000 characters.', '"Note" exceeds maximum length of 2000 characters')
|
|
14
15
|
.replace('Value in [members.subscribed] must be one of true, false, 0 or 1.', 'Value in "Subscribed to emails" must be "true" or "false"')
|
|
15
16
|
.replace('Validation (isEmail) failed for email', 'Invalid email address')
|
|
16
|
-
.replace(/No such customer:[
|
|
17
|
+
.replace(/No such customer:[\s\S]*/, 'Could not find Stripe customer');
|
|
18
|
+
}
|
|
19
|
+
function humaniseError(row) {
|
|
20
|
+
return row.errors.map(humaniseReason).join('\n');
|
|
17
21
|
}
|
|
18
22
|
function stringifyLabels(labels) {
|
|
19
23
|
return labels.map(label => (typeof label === 'string' ? label : label.name)).join(',');
|
|
@@ -40,7 +44,7 @@ function toErrorReportRow(row) {
|
|
|
40
44
|
labels: stringifyLabels(row.labels),
|
|
41
45
|
tiers: '',
|
|
42
46
|
gift_id: row.gift_id || null,
|
|
43
|
-
error: humaniseError(row
|
|
47
|
+
error: humaniseError(row)
|
|
44
48
|
};
|
|
45
49
|
}
|
|
46
50
|
// The error report attached to the completion email: the failed rows as CSV. It shares
|
|
@@ -34,14 +34,19 @@ interface EmailPayload {
|
|
|
34
34
|
|
|
35
35
|
// Turn an ORM validation error into copy a member manager can act on. Presentation
|
|
36
36
|
// only: the API response keeps the raw messages, and just the emailed report is
|
|
37
|
-
// rewritten.
|
|
38
|
-
|
|
37
|
+
// rewritten. Takes one reason at a time, so a rewrite matching to the end of its input
|
|
38
|
+
// cannot consume the reason after it.
|
|
39
|
+
function humaniseReason(message: string): string {
|
|
39
40
|
return message
|
|
40
41
|
.replace('Value in [members.email] cannot be blank.', 'Missing email address')
|
|
41
42
|
.replace('Value in [members.note] exceeds maximum length of 2000 characters.', '"Note" exceeds maximum length of 2000 characters')
|
|
42
43
|
.replace('Value in [members.subscribed] must be one of true, false, 0 or 1.', 'Value in "Subscribed to emails" must be "true" or "false"')
|
|
43
44
|
.replace('Validation (isEmail) failed for email', 'Invalid email address')
|
|
44
|
-
.replace(/No such customer:[
|
|
45
|
+
.replace(/No such customer:[\s\S]*/, 'Could not find Stripe customer');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function humaniseError(row: ImportErrorRow): string {
|
|
49
|
+
return row.errors.map(humaniseReason).join('\n');
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
// One row of the fixed, member-vocabulary part of the error report, in emit order.
|
|
@@ -93,7 +98,7 @@ function toErrorReportRow(row: ImportErrorRow): ErrorReportRow {
|
|
|
93
98
|
labels: stringifyLabels(row.labels),
|
|
94
99
|
tiers: '',
|
|
95
100
|
gift_id: row.gift_id || null,
|
|
96
|
-
error: humaniseError(row
|
|
101
|
+
error: humaniseError(row)
|
|
97
102
|
};
|
|
98
103
|
}
|
|
99
104
|
|
|
@@ -165,7 +165,7 @@ class MembersCSVImporter {
|
|
|
165
165
|
// before any member write -- so there is no reason to hold a transaction
|
|
166
166
|
// across it, and doing so would deadlock the single-connection SQLite pool.
|
|
167
167
|
const customFieldPlan = activeCustomFields.length > 0
|
|
168
|
-
? await this._customFields.planWrite((0, csv_2.fieldValuesFromCsvRow)(activeCustomFields, row, csv_1.stripFormulaGuard))
|
|
168
|
+
? await namingTheColumn(() => this._customFields.planWrite((0, csv_2.fieldValuesFromCsvRow)(activeCustomFields, row, csv_1.stripFormulaGuard)))
|
|
169
169
|
: [];
|
|
170
170
|
trx = await this._knex.transaction(undefined, { doNotRejectOnRollback: false });
|
|
171
171
|
const options = { transacting: trx, context: IMPORT_CONTEXT };
|
|
@@ -265,15 +265,16 @@ class MembersCSVImporter {
|
|
|
265
265
|
}
|
|
266
266
|
catch (error) {
|
|
267
267
|
const errorList = Array.isArray(error) ? error : [error];
|
|
268
|
-
const
|
|
268
|
+
const reasons = errorList
|
|
269
269
|
.map(e => (typeof e === 'object' && e !== null && 'message' in e ? e.message : undefined))
|
|
270
|
-
.
|
|
270
|
+
.filter((message) => typeof message === 'string');
|
|
271
|
+
const errorMessage = reasons.join('\n');
|
|
271
272
|
// trx is unset if the row failed before the transaction opened (a bad
|
|
272
273
|
// custom field value or gift combination).
|
|
273
274
|
if (trx) {
|
|
274
275
|
await trx.rollback();
|
|
275
276
|
}
|
|
276
|
-
importErrors.push({ ...row, error: errorMessage });
|
|
277
|
+
importErrors.push({ ...row, error: errorMessage, errors: reasons });
|
|
277
278
|
}
|
|
278
279
|
}
|
|
279
280
|
await Promise.all(archivableStripePriceIds.map(id => this._stripe.archivePrice(id)));
|
|
@@ -287,6 +288,26 @@ class MembersCSVImporter {
|
|
|
287
288
|
return { imported: 0, errors: importErrors };
|
|
288
289
|
}
|
|
289
290
|
}
|
|
291
|
+
// A row error is read next to a spreadsheet, and a value rejection states only what the
|
|
292
|
+
// value should be — the same sentence Admin shows under a single input, where the field is
|
|
293
|
+
// already on screen. `property` is the dotted path a default export writes as its column
|
|
294
|
+
// header, so prefixing with it names the column a publisher has to go and fix.
|
|
295
|
+
//
|
|
296
|
+
// A rejection about the whole request carries the bare namespace and names no field after
|
|
297
|
+
// it, so it is left alone rather than made to point at a column that does not exist.
|
|
298
|
+
async function namingTheColumn(plan) {
|
|
299
|
+
try {
|
|
300
|
+
return await plan();
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
const { property, message } = (error ?? {});
|
|
304
|
+
const namesAField = typeof property === 'string' && property.includes('.');
|
|
305
|
+
if (!namesAField || typeof message !== 'string') {
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
throw new errors.DataImportError({ message: `${property}: ${message}` });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
290
311
|
function wrapGiftError(error) {
|
|
291
312
|
const err = error;
|
|
292
313
|
const message = (err && typeof err.message === 'string' && err.message) || tpl(messages.giftReassignFailed);
|
|
@@ -99,8 +99,7 @@ type CustomFieldPlan = unknown;
|
|
|
99
99
|
// The custom fields collaborator as the import needs it. activeFields is the field set a
|
|
100
100
|
// custom_fields.* column is read against, empty when the feature is off; planWrite
|
|
101
101
|
// validates a row's values (throwing so the row fails whole) and applyWrite persists
|
|
102
|
-
// them,
|
|
103
|
-
// row's transaction.
|
|
102
|
+
// them, touching only the parts the row named, both on the row's transaction.
|
|
104
103
|
export interface CustomFieldsImport {
|
|
105
104
|
activeFields(): Promise<CsvField[]>;
|
|
106
105
|
planWrite(values: Record<string, unknown>): Promise<CustomFieldPlan[]>;
|
|
@@ -333,7 +332,7 @@ class MembersCSVImporter {
|
|
|
333
332
|
// before any member write -- so there is no reason to hold a transaction
|
|
334
333
|
// across it, and doing so would deadlock the single-connection SQLite pool.
|
|
335
334
|
const customFieldPlan = activeCustomFields.length > 0
|
|
336
|
-
? await this._customFields.planWrite(fieldValuesFromCsvRow(activeCustomFields, row, stripFormulaGuard))
|
|
335
|
+
? await namingTheColumn(() => this._customFields.planWrite(fieldValuesFromCsvRow(activeCustomFields, row, stripFormulaGuard)))
|
|
337
336
|
: [];
|
|
338
337
|
|
|
339
338
|
trx = await this._knex.transaction(undefined, {doNotRejectOnRollback: false});
|
|
@@ -433,15 +432,16 @@ class MembersCSVImporter {
|
|
|
433
432
|
imported += 1;
|
|
434
433
|
} catch (error) {
|
|
435
434
|
const errorList: unknown[] = Array.isArray(error) ? error : [error];
|
|
436
|
-
const
|
|
435
|
+
const reasons = errorList
|
|
437
436
|
.map(e => (typeof e === 'object' && e !== null && 'message' in e ? e.message : undefined))
|
|
438
|
-
.
|
|
437
|
+
.filter((message): message is string => typeof message === 'string');
|
|
438
|
+
const errorMessage = reasons.join('\n');
|
|
439
439
|
// trx is unset if the row failed before the transaction opened (a bad
|
|
440
440
|
// custom field value or gift combination).
|
|
441
441
|
if (trx) {
|
|
442
442
|
await trx.rollback();
|
|
443
443
|
}
|
|
444
|
-
importErrors.push({...row, error: errorMessage});
|
|
444
|
+
importErrors.push({...row, error: errorMessage, errors: reasons});
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
|
|
@@ -458,6 +458,26 @@ class MembersCSVImporter {
|
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
+
// A row error is read next to a spreadsheet, and a value rejection states only what the
|
|
462
|
+
// value should be — the same sentence Admin shows under a single input, where the field is
|
|
463
|
+
// already on screen. `property` is the dotted path a default export writes as its column
|
|
464
|
+
// header, so prefixing with it names the column a publisher has to go and fix.
|
|
465
|
+
//
|
|
466
|
+
// A rejection about the whole request carries the bare namespace and names no field after
|
|
467
|
+
// it, so it is left alone rather than made to point at a column that does not exist.
|
|
468
|
+
async function namingTheColumn<T>(plan: () => Promise<T>): Promise<T> {
|
|
469
|
+
try {
|
|
470
|
+
return await plan();
|
|
471
|
+
} catch (error) {
|
|
472
|
+
const {property, message} = (error ?? {}) as {property?: unknown; message?: unknown};
|
|
473
|
+
const namesAField = typeof property === 'string' && property.includes('.');
|
|
474
|
+
if (!namesAField || typeof message !== 'string') {
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
throw new errors.DataImportError({message: `${property}: ${message}`});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
461
481
|
function wrapGiftError(error: unknown) {
|
|
462
482
|
const err = error as {message?: unknown};
|
|
463
483
|
const message = (err && typeof err.message === 'string' && err.message) || tpl(messages.giftReassignFailed);
|
|
@@ -42,7 +42,11 @@ export type MemberImportRow = z.infer<typeof memberImportRowSchema>;
|
|
|
42
42
|
|
|
43
43
|
// A row that failed to import, carrying the raw message that stopped it. The message is
|
|
44
44
|
// left raw here; turning it into human copy is a presentation concern.
|
|
45
|
-
|
|
45
|
+
// A row can fail for more than one reason. `errors` is the list; `error` is that list
|
|
46
|
+
// written out for the error report's single cell. Two fields because a reason may quote a
|
|
47
|
+
// cell the publisher wrote, and a CSV cell legally holds commas and newlines, so no
|
|
48
|
+
// separator survives being split back apart.
|
|
49
|
+
export type ImportErrorRow = MemberImportRow & {error: string; errors: string[]};
|
|
46
50
|
|
|
47
51
|
// The persisted import label as plain data -- taken off the Bookshelf model before it
|
|
48
52
|
// leaves the kernel, so the result and the email carry only what they show.
|
|
@@ -52,11 +52,7 @@ function makeImporter(deps) {
|
|
|
52
52
|
const customFields = {
|
|
53
53
|
activeFields: async () => (labs.isSet('membersCustomFields') ? deps.customFields.definitions.browse() : []),
|
|
54
54
|
planWrite: values => deps.customFields.values.planWrite(values),
|
|
55
|
-
|
|
56
|
-
// and leaves the rest alone, the same rule a blank `name` column follows. Without
|
|
57
|
-
// it a file naming one address column would replace the whole address, clearing
|
|
58
|
-
// five sub-fields it never mentioned.
|
|
59
|
-
applyWrite: (memberId, plan, executor) => deps.customFields.values.applyWrite(memberId, plan, executor, { mergeComposites: true })
|
|
55
|
+
applyWrite: (memberId, plan, executor) => deps.customFields.values.applyWrite(memberId, plan, { executor })
|
|
60
56
|
};
|
|
61
57
|
return new importer_1.default({
|
|
62
58
|
knex: deps.knex,
|
|
@@ -33,7 +33,7 @@ interface ImporterServices {
|
|
|
33
33
|
definitions: {browse(): Promise<CsvField[]>};
|
|
34
34
|
values: {
|
|
35
35
|
planWrite(values: Record<string, unknown>): Promise<unknown[]>;
|
|
36
|
-
applyWrite(memberId: string, plan: unknown[],
|
|
36
|
+
applyWrite(memberId: string, plan: unknown[], options?: {executor?: Knex}): Promise<void>;
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
39
|
}
|
|
@@ -87,11 +87,7 @@ export function makeImporter(deps: ImporterServices) {
|
|
|
87
87
|
const customFields: CustomFieldsImport = {
|
|
88
88
|
activeFields: async () => (labs.isSet('membersCustomFields') ? deps.customFields.definitions.browse() : []),
|
|
89
89
|
planWrite: values => deps.customFields.values.planWrite(values),
|
|
90
|
-
|
|
91
|
-
// and leaves the rest alone, the same rule a blank `name` column follows. Without
|
|
92
|
-
// it a file naming one address column would replace the whole address, clearing
|
|
93
|
-
// five sub-fields it never mentioned.
|
|
94
|
-
applyWrite: (memberId, plan, executor) => deps.customFields.values.applyWrite(memberId, plan, executor, {mergeComposites: true})
|
|
90
|
+
applyWrite: (memberId, plan, executor) => deps.customFields.values.applyWrite(memberId, plan, {executor})
|
|
95
91
|
};
|
|
96
92
|
|
|
97
93
|
return new MembersCSVImporter({
|
|
@@ -3,19 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.activeFields = activeFields;
|
|
4
4
|
const schema_1 = require("./schema");
|
|
5
5
|
const FIELDS_TABLE = 'members_custom_fields';
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// fields must stay out of every read and every write, and that's enforced by a
|
|
11
|
-
// filter, not a database constraint — nothing stops a value row referencing an
|
|
12
|
-
// archived field, so a query that forgets the filter is a silent bug. Keeping the
|
|
13
|
-
// filter in one builder means there's one place to get it right.
|
|
14
|
-
/**
|
|
15
|
-
* The active field definitions. Takes the executor — a knex instance or a
|
|
16
|
-
* transaction — so the same query runs standalone or inside a write's
|
|
17
|
-
* transaction; callers chain their own `select`/`orderBy`/`whereIn` onto it.
|
|
18
|
-
*/
|
|
6
|
+
// Archived fields must stay out of every read and write, and nothing in the database
|
|
7
|
+
// enforces that — no constraint stops a value row referencing an archived field. A query
|
|
8
|
+
// that forgets the filter is a silent bug, so the filter lives in one place.
|
|
9
|
+
/** Takes the executor so the same query runs standalone or inside a write's transaction. */
|
|
19
10
|
function activeFields(db) {
|
|
20
11
|
return db(FIELDS_TABLE).where('status', schema_1.FIELD_STATUS.active);
|
|
21
12
|
}
|
|
@@ -3,20 +3,11 @@ import {FIELD_STATUS} from './schema';
|
|
|
3
3
|
|
|
4
4
|
const FIELDS_TABLE = 'members_custom_fields';
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// The `status = 'active'` filter is the one invariant worth centralising: archived
|
|
10
|
-
// fields must stay out of every read and every write, and that's enforced by a
|
|
11
|
-
// filter, not a database constraint — nothing stops a value row referencing an
|
|
12
|
-
// archived field, so a query that forgets the filter is a silent bug. Keeping the
|
|
13
|
-
// filter in one builder means there's one place to get it right.
|
|
6
|
+
// Archived fields must stay out of every read and write, and nothing in the database
|
|
7
|
+
// enforces that — no constraint stops a value row referencing an archived field. A query
|
|
8
|
+
// that forgets the filter is a silent bug, so the filter lives in one place.
|
|
14
9
|
|
|
15
|
-
/**
|
|
16
|
-
* The active field definitions. Takes the executor — a knex instance or a
|
|
17
|
-
* transaction — so the same query runs standalone or inside a write's
|
|
18
|
-
* transaction; callers chain their own `select`/`orderBy`/`whereIn` onto it.
|
|
19
|
-
*/
|
|
10
|
+
/** Takes the executor so the same query runs standalone or inside a write's transaction. */
|
|
20
11
|
export function activeFields(db: Knex) {
|
|
21
12
|
return db(FIELDS_TABLE).where('status', FIELD_STATUS.active);
|
|
22
13
|
}
|