ghost 6.55.0 → 6.56.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/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
- package/components/tryghost-kg-default-transforms-1.3.3.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-nql-string-0.0.0.tgz +0 -0
- package/core/built/admin/assets/{Combination-CJG7Flp-.js → Combination-BCm9FL4o.js} +1 -1
- package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
- package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
- package/core/built/admin/assets/{account-migration-sjHLx5dy.js → account-migration-D3O_yVck.js} +1 -1
- package/core/built/admin/assets/activitypub/{account-migration-DoYXtnZ8.js → account-migration-D8FbP-Z-.js} +7 -7
- package/core/built/admin/assets/activitypub/activitypub.js +6 -6
- package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-11EA1VSs.js} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-jfbLG-xe.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-Bxeh6MKW.js → bluesky-sharing-f1jTQe2P.js} +16 -16
- package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-nmTOPY9M.js} +3 -3
- package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-BCwo9ep3.js} +2 -2
- package/core/built/admin/assets/activitypub/{dist-D8sFfdpC.js → dist-BEfX-0aW.js} +12 -12
- package/core/built/admin/assets/activitypub/{domain-CWueFW6q.js → domain-AaDlZlrA.js} +9 -9
- package/core/built/admin/assets/activitypub/{edit-profile-BGxPU7N5.js → edit-profile-DPi8Tl5M.js} +945 -945
- package/core/built/admin/assets/activitypub/{explore-DBK-u0Ch.js → explore-CxTzjILQ.js} +8 -8
- package/core/built/admin/assets/activitypub/{feed-Bcir5RQZ.js → feed-DPv5x2MX.js} +33 -33
- package/core/built/admin/assets/activitypub/{field-BV-CIT3d.js → field-DbgA8lat.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-BWuZoYkA.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-35VDQ2K2.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-yC0nfvzl.js → inbox-DSrF_i1O.js} +85 -85
- package/core/built/admin/assets/activitypub/{label-DTRS9TDk.js → label-CPjFBbo7.js} +3 -3
- package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-Dwl9t0pb.js → moderation-BkDC1XVZ.js} +11 -11
- package/core/built/admin/assets/activitypub/{note-B-zKsZ7r.js → note-xEyH4xT-.js} +7 -7
- package/core/built/admin/assets/activitypub/{notifications-DjJZD54t.js → notifications-DClaL3ui.js} +9 -9
- package/core/built/admin/assets/activitypub/{preferences-CoU8vs6d.js → preferences-DpnOa-4Z.js} +20 -20
- package/core/built/admin/assets/activitypub/{profile-BAsaJekx.js → profile-Dvi9mz-6.js} +237 -237
- package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cs9ySd6Z.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-CLH3OVTP.js → routes-DFBeTnfn.js} +1139 -1150
- package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-C5u_VjzY.js} +3 -3
- package/core/built/admin/assets/activitypub/{settings-DA6pNJ-Q.js → settings-CV2fFu6D.js} +13 -13
- package/core/built/admin/assets/activitypub/{site-CQD42Pw5.js → site-_TgDd_EM.js} +81 -81
- package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-BfnNL2oF.js} +5 -5
- package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DLndZkO-.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-A-PZTtRE.js} +10 -10
- package/core/built/admin/assets/activitypub/{tabs-Cg0saow1.js → tabs-BUbz7tea.js} +317 -315
- package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-BdX_xYYT.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-EwuRemHh.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-cy4ACBc4.js} +92 -90
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-Br4aOWOh.js → use-reply-chain-data-CyFcGfan.js} +5 -5
- package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-7zk8mV2p.js} +2 -2
- package/core/built/admin/assets/{audience-DjEWNhp3.js → audience-CkBb2LlO.js} +1 -1
- package/core/built/admin/assets/{automated-emails-DRy7qY-J.js → automated-emails-6t2bIRWf.js} +1 -1
- package/core/built/admin/assets/{automations-Bx3VcFIL.js → automations-cMllU545.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-ClMd0-Ft.js → bluesky-sharing-DPGpyD-G.js} +1 -1
- package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
- package/core/built/admin/assets/{breadcrumb-BD0U2ygg.js → breadcrumb-DIfcF5ow.js} +1 -1
- package/core/built/admin/assets/build-CsiH26uS.js +1 -0
- package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
- package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-fjQKRVyY.js → chart-helpers-DV0FkdcD.js} +1 -1
- package/core/built/admin/assets/{checkbox-CWa28lgO.js → checkbox-BIl077a0.js} +1 -1
- package/core/built/admin/assets/{chunk.481.6c7395a49484bb4583aa.js → chunk.332.05991a101d67f732dec3.js} +43 -42
- package/core/built/admin/assets/chunk.524.87b52f4fada529df2c4c.js +25 -0
- package/core/built/admin/assets/chunk.582.340bcca873e806cbd4bb.js +27 -0
- package/core/built/admin/assets/{code-editor-view-CCr0rBWS.js → code-editor-view-C0aWNwdl.js} +1 -1
- package/core/built/admin/assets/comments-qwhIjH3c.js +1 -0
- package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
- package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
- package/core/built/admin/assets/{date-range-select-BFp58EMk.js → date-range-select-dXc-GsVe.js} +1 -1
- package/core/built/admin/assets/{disabled-sources-indicator-CUxMfiPa.js → disabled-sources-indicator-DYAXW-6V.js} +1 -1
- package/core/built/admin/assets/dist-BUKmD51u.js +1 -0
- package/core/built/admin/assets/{dist-2Tnz2Yfq.js → dist-CNlMj7ML.js} +1 -1
- package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
- package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS0.js} +1 -1
- package/core/built/admin/assets/domain-B5ogXUBa.js +1 -0
- package/core/built/admin/assets/{dropzone-DlK-K2DQ.js → dropzone-BnFG7dDP.js} +1 -1
- package/core/built/admin/assets/{edit-profile-BKT-REJw.js → edit-profile-B07jCZy9.js} +1 -1
- package/core/built/admin/assets/editor-DDYSOXGN.js +8 -0
- package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
- package/core/built/admin/assets/{explore-ClUpJZOp.js → explore-Ca1y8HW_.js} +1 -1
- package/core/built/admin/assets/{feed-DF92dUMP.js → feed-x4iFMBP4.js} +1 -1
- package/core/built/admin/assets/{field-Do7lieBA.js → field-ChCAabZg.js} +1 -1
- package/core/built/admin/assets/filter-sources-LdtH1e-T.js +1 -0
- package/core/built/admin/assets/filters-CLehPo_D.js +1 -0
- package/core/built/admin/assets/filters-CjVKRP8U.js +1 -0
- package/core/built/admin/assets/{ghost-c4057474943effc568c5659ccc4909d7.js → ghost-7f01131115186d1af84ead76e79646dd.js} +149 -129
- package/core/built/admin/assets/{ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
- package/core/built/admin/assets/{ghost-dark-db36d795d21c61426837df40af270966.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
- package/core/built/admin/assets/{gift-link-modal-f9VIMNDv.js → gift-link-modal-Buqh2vXh.js} +1 -1
- package/core/built/admin/assets/{growth-B3I6gNLm.js → growth-DajOBdN0.js} +1 -1
- package/core/built/admin/assets/{growth-CVw1My4S.js → growth-Dy2UOSay.js} +2 -2
- package/core/built/admin/assets/{inbox-CFBSu4Ed.js → inbox-BUOLDoji.js} +12 -12
- package/core/built/admin/assets/index-08n05-rc.js +67 -0
- package/core/built/admin/assets/index-DgUzgFvS.css +1 -0
- package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
- package/core/built/admin/assets/input-group-uSK3XT7b.js +1 -0
- package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9535 -9527
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
- package/core/built/admin/assets/{koenig-lexical-f8vHRYvz.js → koenig-lexical-BFc4yFed.js} +86 -86
- package/core/built/admin/assets/{kpi-card-DSap9rAy.js → kpi-card-BN-E6WYX.js} +1 -1
- package/core/built/admin/assets/{kpi-card-DU0rn9Cj.js → kpi-card-CCt-qeWD.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-Dz33AV6h.js → kpi-helpers-DlxTbTs4.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-BtTTrKvJ.js → kpi-tabs-BlcUttpn.js} +1 -1
- package/core/built/admin/assets/{label-DS8Mi6G5.js → label-AVrxwmsz.js} +1 -1
- package/core/built/admin/assets/{label-picker-1DvSuLA1.js → label-picker-DVnpu_IF.js} +1 -1
- package/core/built/admin/assets/labels-DAIqKed7.js +1 -0
- package/core/built/admin/assets/layout-B0YLh7R9.js +1 -0
- package/core/built/admin/assets/link-helpers-D9LsDOkG.js +1 -0
- package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
- package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +1 -0
- package/core/built/admin/assets/member-detail-CIVAdKXf.js +1 -0
- package/core/built/admin/assets/{members-CQpVFJu4.js → members-oLPiSkXD.js} +2 -2
- package/core/built/admin/assets/modals-CB7v-k3T.js +31 -0
- package/core/built/admin/assets/{moderation-DySbgyUS.js → moderation-CQ3urTFE.js} +1 -1
- package/core/built/admin/assets/newsletter-rbeDX9Wk.js +1 -0
- package/core/built/admin/assets/newsletters-vt3Ni1Ov.js +1 -0
- package/core/built/admin/assets/{note-C8kxrz6w.js → note-Dk877BTv.js} +1 -1
- package/core/built/admin/assets/{notifications-CgtSfX7t.js → notifications-CPlLjPxO.js} +1 -1
- package/core/built/admin/assets/{onboarding-route-4qtziA9d.js → onboarding-route-DdStOtjy.js} +1 -1
- package/core/built/admin/assets/{overview-Bf2sbznq.js → overview--4V-kx9c.js} +1 -1
- package/core/built/admin/assets/overview-DSLv30oz.js +1 -0
- package/core/built/admin/assets/{page-header-B3Wb9Jqv.js → page-header-pp4dWmOS.js} +1 -1
- package/core/built/admin/assets/{post-analytics-We-ko5Cb.js → post-analytics-BMJrI81R.js} +1 -1
- package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-B6UynTGb.js → post-analytics-provider-Btml5vJI.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-DFfO93_0.js → post-share-modal-AcUZkbfL.js} +1 -1
- package/core/built/admin/assets/preferences-Ru4CRPHC.js +5 -0
- package/core/built/admin/assets/profile-BNb1oj7D.js +1 -0
- package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-BmEZf6c6.js} +1 -1
- package/core/built/admin/assets/select-B0HteHrp.js +1 -0
- package/core/built/admin/assets/settings-B1C9Q3wo.js +4 -0
- package/core/built/admin/assets/{settings-WXNeWyU1.js → settings-BGgk7oCI.js} +1 -1
- package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
- package/core/built/admin/assets/{share-modal-DsgW3wbg.js → share-modal-BeuydaBf.js} +1 -1
- package/core/built/admin/assets/social-urls-DA2lXrhW.js +8 -0
- package/core/built/admin/assets/{sort-button-Bb_FCl7e.js → sort-button-Df4t2AL8.js} +1 -1
- package/core/built/admin/assets/{source-icon-BeaRxUn9.js → source-icon-ajoaKb41.js} +1 -1
- package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
- package/core/built/admin/assets/{stats-Bjj_kmJ1.js → stats-C_lfxZAw.js} +1 -1
- package/core/built/admin/assets/{stats-view-BxPbW6K1.js → stats-view-Cxot1iD8.js} +1 -1
- package/core/built/admin/assets/{step-1-B1e3-1F8.js → step-1-ogAA4lDr.js} +1 -1
- package/core/built/admin/assets/{step-2-C5WaeBJo.js → step-2-C5Xini-7.js} +1 -1
- package/core/built/admin/assets/{step-3-B46iVPwu.js → step-3-CZv1FV81.js} +2 -2
- package/core/built/admin/assets/{switch-z5Y6Uzt6.js → switch-CyQcwMAv.js} +1 -1
- package/core/built/admin/assets/{table-CB-4_HBJ.js → table-DFj1wiYv.js} +1 -1
- package/core/built/admin/assets/{tabs-CCEgKYzq.js → tabs-D4y1gqUd.js} +1 -1
- package/core/built/admin/assets/{tags-Cg2VJGmG.js → tags-Dmft-Y4O.js} +1 -1
- package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
- package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
- package/core/built/admin/assets/{toggle-group-b3IlxvhV.js → toggle-group-R2QMFvXW.js} +1 -1
- package/core/built/admin/assets/{topic-filter-DAFT87tQ.js → topic-filter-DGDxDalo.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data-S0FTd43s.js → use-analytics-data-C76v4LIp.js} +1 -1
- package/core/built/admin/assets/use-filter-params-WrdhAISv.js +1 -0
- package/core/built/admin/assets/{use-growth-stats-CIaiHOVb.js → use-growth-stats-aNXJOmQr.js} +1 -1
- package/core/built/admin/assets/{use-post-referrers-Cq2vBPrA.js → use-post-referrers-CG0SZioj.js} +1 -1
- package/core/built/admin/assets/{use-reply-chain-data-DBDRtGu-.js → use-reply-chain-data-RLdPKRmS.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-CjYNxdwe.js → use-simple-pagination-DgcF69KI.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-falHAtuK.js → use-tinybird-query-C1R_Kbmy.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-D7-8ND1y.js → use-tinybird-token-C9fPjVM_.js} +1 -1
- package/core/built/admin/assets/{virtual-list-CvnC9zF2.js → virtual-list-BmTUzeoF.js} +1 -1
- package/core/built/admin/assets/{web-B_5GZRvH.js → web-BqW0b0_C.js} +1 -1
- package/core/built/admin/assets/web-Cy5GKMWM.js +2 -0
- package/core/built/admin/index.html +16 -16
- package/core/frontend/services/data/fetch-data.js +10 -5
- package/core/frontend/services/routing/api-adapter.js +115 -0
- package/core/frontend/services/routing/api-adapter.ts +157 -0
- package/core/frontend/services/routing/collection-router.js +5 -3
- package/core/frontend/services/routing/controllers/index.js +1 -1
- package/core/frontend/services/routing/controllers/rss.js +26 -31
- package/core/frontend/services/routing/controllers/rss.ts +66 -0
- package/core/frontend/services/routing/controllers/static.js +8 -7
- package/core/frontend/services/routing/parent-router.js +17 -4
- package/core/frontend/services/routing/permalink-adapter.js +26 -0
- package/core/frontend/services/routing/permalink-adapter.ts +23 -0
- package/core/frontend/services/routing/registry.js +59 -33
- package/core/frontend/services/routing/router-manager.js +9 -13
- package/core/frontend/services/routing/static-routes-router.js +4 -4
- package/core/frontend/services/routing/taxonomy-router.js +6 -2
- package/core/server/data/migrations/versions/6.56/2026-08-03-19-27-51-add-automation-run-step-id-to-automated-email-recipients.js +8 -0
- package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
- package/core/server/data/schema/fixtures/fixtures.json +2 -2
- package/core/server/data/schema/schema.js +1 -0
- package/core/server/models/post.js +10 -1
- package/core/server/services/automations/automations-repository.ts +3 -1
- package/core/server/services/automations/database-automations-repository.js +33 -14
- package/core/server/services/automations/database-automations-repository.ts +41 -15
- package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
- package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
- package/core/server/services/automations/poll.js +3 -1
- package/core/server/services/automations/poll.ts +4 -1
- package/core/server/services/automations/service.js +5 -1
- package/core/server/services/automations/service.ts +5 -1
- package/core/server/services/gift-links/CONTEXT.md +9 -0
- package/core/server/services/gifts/CONTEXT.md +55 -0
- package/core/server/services/gifts/README.md +28 -0
- package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
- package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
- package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
- package/core/server/services/gifts/gift-checkout-offer.js +74 -0
- package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
- package/core/server/services/gifts/gift-codec.js +63 -0
- package/core/server/services/gifts/gift-codec.ts +62 -0
- package/core/server/services/gifts/gift-controller.js +6 -31
- package/core/server/services/gifts/gift-controller.ts +6 -69
- package/core/server/services/gifts/gift-email-service.ts +3 -2
- package/core/server/services/gifts/gift-schema.js +34 -0
- package/core/server/services/gifts/gift-schema.ts +39 -0
- package/core/server/services/gifts/gift-service-wrapper.js +13 -11
- package/core/server/services/gifts/gift-service.js +265 -52
- package/core/server/services/gifts/gift-service.ts +431 -97
- package/core/server/services/gifts/gift.ts +16 -39
- package/core/server/services/link-tracking/link-click-repository.js +6 -1
- package/core/server/services/link-tracking/link-click-tracking-service.js +12 -3
- package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +4 -3
- package/core/server/services/member-welcome-emails/service.js +7 -3
- package/core/server/services/members/api.js +1 -2
- package/core/server/services/members/import-export/import/importer.ts +3 -2
- package/core/server/services/members/import-export/index.js +10 -2
- package/core/server/services/members/import-export/index.ts +14 -4
- package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
- package/core/server/services/members/members-api/members-api.js +13 -7
- package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
- package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
- package/core/server/services/members/members-api/services/payments-service.js +6 -68
- package/core/server/services/members-custom-fields/schema.js +8 -1
- package/core/server/services/members-custom-fields/schema.ts +8 -0
- package/core/server/services/members-custom-fields/values-service.js +118 -2
- package/core/server/services/members-custom-fields/values-service.ts +134 -4
- package/core/server/services/oembed/oembed-service.js +44 -3
- package/core/server/services/posts/posts-service.js +11 -0
- package/core/server/services/route-settings/dynamic-routing-service.js +1 -3
- package/core/server/services/route-settings/route-settings-parser.js +25 -25
- package/core/server/services/route-settings/route-settings-parser.ts +27 -27
- package/core/server/services/route-settings/validation-errors.js +68 -4
- package/core/server/services/route-settings/validation-errors.ts +79 -4
- package/core/server/services/stats/posts-stats-service.js +27 -23
- package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
- package/core/server/services/stripe/stripe-api.js +10 -0
- package/core/server/web/gift-preview/controller.js +10 -25
- package/core/shared/labs.js +2 -2
- package/package.json +10 -9
- package/pnpm-lock.yaml +28 -18
- package/pnpm-workspace.yaml +5 -4
- package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
- package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +0 -1
- package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +0 -25
- package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +0 -27
- package/core/built/admin/assets/comments-DyUSMUdj.js +0 -1
- package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
- package/core/built/admin/assets/domain-C0bV2UV8.js +0 -1
- package/core/built/admin/assets/editor-Dp865O-4.js +0 -8
- package/core/built/admin/assets/filter-sources-BV2rnpfA.js +0 -1
- package/core/built/admin/assets/filters-CUXJ2Nm9.js +0 -1
- package/core/built/admin/assets/filters-DfbIWc_l.js +0 -1
- package/core/built/admin/assets/index-9Cw8z3rA.css +0 -1
- package/core/built/admin/assets/index-BGG7NckO.js +0 -67
- package/core/built/admin/assets/input-group-BSgG_lVw.js +0 -1
- package/core/built/admin/assets/labels-CDSUt4J_.js +0 -1
- package/core/built/admin/assets/layout-yQEi2qDM.js +0 -1
- package/core/built/admin/assets/link-helpers-7EYqvvHs.js +0 -1
- package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +0 -1
- package/core/built/admin/assets/member-detail-BAq7gDaa.js +0 -1
- package/core/built/admin/assets/modals-ReORvWnu.js +0 -31
- package/core/built/admin/assets/newsletter-CJQvtHqN.js +0 -1
- package/core/built/admin/assets/newsletters-B3QQXfGT.js +0 -1
- package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
- package/core/built/admin/assets/overview-CBfePxcr.js +0 -1
- package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +0 -1
- package/core/built/admin/assets/preferences-DJPfHEGu.js +0 -5
- package/core/built/admin/assets/profile-DqBQp-gT.js +0 -1
- package/core/built/admin/assets/select-C5_xEt5b.js +0 -1
- package/core/built/admin/assets/settings-BwiVIXau.js +0 -4
- package/core/built/admin/assets/social-urls-BQPFEk1_.js +0 -8
- package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
- package/core/built/admin/assets/use-filter-params-Gpoq2E1L.js +0 -1
- package/core/built/admin/assets/web-jFYWCKDn.js +0 -2
- package/core/server/services/gifts/gift-repository.js +0 -2
- package/core/server/services/gifts/gift-repository.ts +0 -29
- package/core/server/services/route-settings/activation-bridge.js +0 -174
- package/core/server/services/route-settings/activation-bridge.ts +0 -250
|
@@ -36,11 +36,11 @@ const SHORTFORM_HELP = 'e.g. data: tag.recipes';
|
|
|
36
36
|
*/
|
|
37
37
|
function validateDataShortForm(value, path) {
|
|
38
38
|
if (!/^[a-zA-Z0-9_]+\.(?:%s|[a-zA-Z0-9_-]+)$/.test(value)) {
|
|
39
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(path), `"${value}" is not a valid data shorthand. Please use resource.slug, e.g. tag.recipes.`, SHORTFORM_HELP);
|
|
39
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(path), `"${value}" is not a valid data shorthand. Please use resource.slug, e.g. tag.recipes.`, { help: SHORTFORM_HELP, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA });
|
|
40
40
|
}
|
|
41
41
|
const resource = value.split('.')[0];
|
|
42
42
|
if (!VALID_SHORTFORM_RESOURCES.includes(resource)) {
|
|
43
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(path), `resource "${resource}" is not supported. Please use ${(0, validation_errors_1.humanList)(VALID_SHORTFORM_RESOURCES)}.`, SHORTFORM_HELP);
|
|
43
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(path), `resource "${resource}" is not supported. Please use ${(0, validation_errors_1.humanList)(VALID_SHORTFORM_RESOURCES)}.`, { help: SHORTFORM_HELP, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_RESOURCE });
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
const DataReadEntrySchema = zod_1.z.object({
|
|
@@ -82,20 +82,20 @@ function parseDataEntry(value, path) {
|
|
|
82
82
|
// is free to change.
|
|
83
83
|
if (issue.code === 'invalid_union' && issue.path[0] === 'type') {
|
|
84
84
|
if (record.type === undefined) {
|
|
85
|
-
throw (0, validation_errors_1.validationError)(at, 'type is required. Please use read or browse.', DATA_ENTRY_HELP);
|
|
85
|
+
throw (0, validation_errors_1.validationError)(at, 'type is required. Please use read or browse.', { help: DATA_ENTRY_HELP, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA });
|
|
86
86
|
}
|
|
87
|
-
throw (0, validation_errors_1.validationError)(at, `type "${record.type}" is not supported. Please use read or browse.`, DATA_ENTRY_HELP);
|
|
87
|
+
throw (0, validation_errors_1.validationError)(at, `type "${record.type}" is not supported. Please use read or browse.`, { help: DATA_ENTRY_HELP, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA });
|
|
88
88
|
}
|
|
89
89
|
if (issue.path.includes('resource')) {
|
|
90
90
|
if (record.resource === undefined) {
|
|
91
|
-
throw (0, validation_errors_1.validationError)(at, `resource is required. Please use ${(0, validation_errors_1.humanList)(VALID_LONGFORM_RESOURCES)}.`, DATA_ENTRY_HELP);
|
|
91
|
+
throw (0, validation_errors_1.validationError)(at, `resource is required. Please use ${(0, validation_errors_1.humanList)(VALID_LONGFORM_RESOURCES)}.`, { help: DATA_ENTRY_HELP, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_RESOURCE });
|
|
92
92
|
}
|
|
93
|
-
throw (0, validation_errors_1.validationError)(at, `resource "${record.resource}" is not supported. Please use ${(0, validation_errors_1.humanList)(VALID_LONGFORM_RESOURCES)}.`, DATA_ENTRY_HELP);
|
|
93
|
+
throw (0, validation_errors_1.validationError)(at, `resource "${record.resource}" is not supported. Please use ${(0, validation_errors_1.humanList)(VALID_LONGFORM_RESOURCES)}.`, { help: DATA_ENTRY_HELP, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_RESOURCE });
|
|
94
94
|
}
|
|
95
95
|
if (issue.path.includes('slug') && !record.slug) {
|
|
96
|
-
throw (0, validation_errors_1.validationError)(at, 'slug is required for read data entries.', DATA_ENTRY_HELP);
|
|
96
|
+
throw (0, validation_errors_1.validationError)(at, 'slug is required for read data entries.', { help: DATA_ENTRY_HELP, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA });
|
|
97
97
|
}
|
|
98
|
-
throw (0, validation_errors_1.toValidationError)(result.error, path, value);
|
|
98
|
+
throw (0, validation_errors_1.toValidationError)(result.error, path, value, validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA);
|
|
99
99
|
}
|
|
100
100
|
// Fields set to an explicitly empty value parse to undefined — drop the
|
|
101
101
|
// keys so "unset" means absent in the domain model and serialized output.
|
|
@@ -107,7 +107,7 @@ function parseDataEntry(value, path) {
|
|
|
107
107
|
}
|
|
108
108
|
return result.data;
|
|
109
109
|
}
|
|
110
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(path), `a data entry must be a shorthand like tag.recipes, or a map with type and resource, but ${(0, validation_errors_1.describeValue)(value)} was provided.`, DATA_ENTRY_HELP);
|
|
110
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(path), `a data entry must be a shorthand like tag.recipes, or a map with type and resource, but ${(0, validation_errors_1.describeValue)(value)} was provided.`, { help: DATA_ENTRY_HELP, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA });
|
|
111
111
|
}
|
|
112
112
|
function parseRouteData(data, path) {
|
|
113
113
|
if (typeof data === 'string') {
|
|
@@ -115,15 +115,15 @@ function parseRouteData(data, path) {
|
|
|
115
115
|
return data;
|
|
116
116
|
}
|
|
117
117
|
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
118
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(path), `data must be a shorthand like tag.recipes, or a map of named data entries, but ${(0, validation_errors_1.describeValue)(data)} was provided.`, DATA_ENTRY_HELP);
|
|
118
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(path), `data must be a shorthand like tag.recipes, or a map of named data entries, but ${(0, validation_errors_1.describeValue)(data)} was provided.`, { help: DATA_ENTRY_HELP, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA });
|
|
119
119
|
}
|
|
120
120
|
const record = data;
|
|
121
121
|
for (const key of Object.keys(record)) {
|
|
122
122
|
if (RESERVED_DATA_KEYS.includes(key)) {
|
|
123
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)([...path, key]), (0, tpl_1.default)(messages.badDataError, { key }), messages.badDataHelp);
|
|
123
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)([...path, key]), (0, tpl_1.default)(messages.badDataError, { key }), { help: messages.badDataHelp, code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA });
|
|
124
124
|
}
|
|
125
125
|
if (key === 'author') {
|
|
126
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)([...path, key]), messages.authorDeprecatedError);
|
|
126
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)([...path, key]), messages.authorDeprecatedError, { code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA });
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
const parsed = {};
|
|
@@ -159,8 +159,8 @@ const routeObjectSchema = (path) => zod_1.z.object({
|
|
|
159
159
|
templates
|
|
160
160
|
};
|
|
161
161
|
// Preserve rss only when the author set it explicitly — the domain model
|
|
162
|
-
// mirrors user intent (unset vs true vs false), so
|
|
163
|
-
//
|
|
162
|
+
// mirrors user intent (unset vs true vs false), so serializing never
|
|
163
|
+
// writes back an `rss` key the author never wrote.
|
|
164
164
|
if (val.rss !== undefined) {
|
|
165
165
|
route.rss = val.rss;
|
|
166
166
|
}
|
|
@@ -237,18 +237,18 @@ const RouteSettingsSchema = zod_1.z.object({
|
|
|
237
237
|
* rejecting :param there would break the former, and the suggested rewrite would
|
|
238
238
|
* silently produce the latter.
|
|
239
239
|
*/
|
|
240
|
-
function validatePath(value, path, example, { allowParamNotation = false } = {}) {
|
|
240
|
+
function validatePath(value, path, example, { allowParamNotation = false, code = validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_PATH } = {}) {
|
|
241
241
|
const at = (0, validation_errors_1.formatLocation)(path);
|
|
242
242
|
if (!value.startsWith('/')) {
|
|
243
|
-
throw (0, validation_errors_1.validationError)(at, `"${value}" is missing a leading slash. Please use e.g. ${example}
|
|
243
|
+
throw (0, validation_errors_1.validationError)(at, `"${value}" is missing a leading slash. Please use e.g. ${example}.`, { code });
|
|
244
244
|
}
|
|
245
245
|
if (!value.endsWith('/')) {
|
|
246
|
-
throw (0, validation_errors_1.validationError)(at, `"${value}" is missing a trailing slash. Please use e.g. ${example}
|
|
246
|
+
throw (0, validation_errors_1.validationError)(at, `"${value}" is missing a trailing slash. Please use e.g. ${example}.`, { code });
|
|
247
247
|
}
|
|
248
248
|
if (!allowParamNotation && /\/:\w+/.test(value)) {
|
|
249
249
|
// Suggest the same path in the notation Ghost expects, rather than a
|
|
250
250
|
// generic example the author then has to translate.
|
|
251
|
-
throw (0, validation_errors_1.validationError)(at, `"${value}" uses the :param notation. Please use "${value.replace(/\/:(\w+)/g, '/{$1}')}"
|
|
251
|
+
throw (0, validation_errors_1.validationError)(at, `"${value}" uses the :param notation. Please use "${value.replace(/\/:(\w+)/g, '/{$1}')}".`, { code });
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
function parseRouteSettings(raw, yamlSource) {
|
|
@@ -264,7 +264,7 @@ function parseRouteSettings(raw, yamlSource) {
|
|
|
264
264
|
const routeLocation = ['routes', path];
|
|
265
265
|
validatePath(path, routeLocation, '/about/', { allowParamNotation: true });
|
|
266
266
|
if (value === null || value === undefined) {
|
|
267
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(routeLocation), 'Please define a template, e.g. /about/: about.');
|
|
267
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(routeLocation), 'Please define a template, e.g. /about/: about.', { code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_TEMPLATE });
|
|
268
268
|
}
|
|
269
269
|
if (typeof value === 'string') {
|
|
270
270
|
routes.push({ type: 'template', path, templates: [value] });
|
|
@@ -276,7 +276,7 @@ function parseRouteSettings(raw, yamlSource) {
|
|
|
276
276
|
}
|
|
277
277
|
const route = routeResult.data;
|
|
278
278
|
if (route.type === 'template' && (!route.templates || route.templates.length === 0) && !route.data && !route.contentType) {
|
|
279
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(routeLocation), 'Please define a template, e.g. /about/: about.');
|
|
279
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(routeLocation), 'Please define a template, e.g. /about/: about.', { code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_TEMPLATE });
|
|
280
280
|
}
|
|
281
281
|
routes.push({ ...route, path });
|
|
282
282
|
}
|
|
@@ -292,9 +292,9 @@ function parseRouteSettings(raw, yamlSource) {
|
|
|
292
292
|
}
|
|
293
293
|
const collection = collectionResult.data;
|
|
294
294
|
if (!collection.permalink) {
|
|
295
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(collectionLocation), 'Please define a permalink route, e.g. permalink: /{slug}/.');
|
|
295
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(collectionLocation), 'Please define a permalink route, e.g. permalink: /{slug}/.', { code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK });
|
|
296
296
|
}
|
|
297
|
-
validatePath(collection.permalink, [...collectionLocation, 'permalink'], '/{slug}/');
|
|
297
|
+
validatePath(collection.permalink, [...collectionLocation, 'permalink'], '/{slug}/', { code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK });
|
|
298
298
|
collections.push({ ...collection, path });
|
|
299
299
|
}
|
|
300
300
|
}
|
|
@@ -303,12 +303,12 @@ function parseRouteSettings(raw, yamlSource) {
|
|
|
303
303
|
for (const [key, value] of Object.entries(rawTaxonomies)) {
|
|
304
304
|
const taxonomyLocation = ['taxonomies', key];
|
|
305
305
|
if (!['tag', 'author'].includes(key)) {
|
|
306
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(taxonomyLocation), 'Unknown taxonomy. Please use tag or author.');
|
|
306
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(taxonomyLocation), 'Unknown taxonomy. Please use tag or author.', { code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_TAXONOMY });
|
|
307
307
|
}
|
|
308
308
|
if (!value) {
|
|
309
|
-
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(taxonomyLocation), `Please define a taxonomy permalink route, e.g. ${key}: /${key}/{slug}
|
|
309
|
+
throw (0, validation_errors_1.validationError)((0, validation_errors_1.formatLocation)(taxonomyLocation), `Please define a taxonomy permalink route, e.g. ${key}: /${key}/{slug}/.`, { code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK });
|
|
310
310
|
}
|
|
311
|
-
validatePath(value, taxonomyLocation, `/${key}/{slug}
|
|
311
|
+
validatePath(value, taxonomyLocation, `/${key}/{slug}/`, { code: validation_errors_1.ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK });
|
|
312
312
|
if (key === 'tag') {
|
|
313
313
|
taxonomies.tag = value;
|
|
314
314
|
}
|
|
@@ -12,8 +12,8 @@ import type {
|
|
|
12
12
|
} from '@tryghost/adapter-base-route-settings';
|
|
13
13
|
import {z} from 'zod';
|
|
14
14
|
import tpl from '@tryghost/tpl';
|
|
15
|
-
import type {PathSegment} from './validation-errors';
|
|
16
|
-
import {describeValue, formatLocation, humanList, toValidationError, validationError} from './validation-errors';
|
|
15
|
+
import type {PathSegment, RouteSettingsErrorCode} from './validation-errors';
|
|
16
|
+
import {describeValue, formatLocation, humanList, ROUTE_SETTINGS_ERROR_CODES, toValidationError, validationError} from './validation-errors';
|
|
17
17
|
|
|
18
18
|
const messages = {
|
|
19
19
|
badDataError: '"{key}" is a reserved key. Please wrap the data definition into a custom name.',
|
|
@@ -49,7 +49,7 @@ function validateDataShortForm(value: string, path: PathSegment[]): void {
|
|
|
49
49
|
throw validationError(
|
|
50
50
|
formatLocation(path),
|
|
51
51
|
`"${value}" is not a valid data shorthand. Please use resource.slug, e.g. tag.recipes.`,
|
|
52
|
-
SHORTFORM_HELP
|
|
52
|
+
{help: SHORTFORM_HELP, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA}
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -58,7 +58,7 @@ function validateDataShortForm(value: string, path: PathSegment[]): void {
|
|
|
58
58
|
throw validationError(
|
|
59
59
|
formatLocation(path),
|
|
60
60
|
`resource "${resource}" is not supported. Please use ${humanList(VALID_SHORTFORM_RESOURCES)}.`,
|
|
61
|
-
SHORTFORM_HELP
|
|
61
|
+
{help: SHORTFORM_HELP, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_RESOURCE}
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -108,20 +108,20 @@ function parseDataEntry(value: unknown, path: PathSegment[]): DataEntry {
|
|
|
108
108
|
// is free to change.
|
|
109
109
|
if (issue.code === 'invalid_union' && issue.path[0] === 'type') {
|
|
110
110
|
if (record.type === undefined) {
|
|
111
|
-
throw validationError(at, 'type is required. Please use read or browse.', DATA_ENTRY_HELP);
|
|
111
|
+
throw validationError(at, 'type is required. Please use read or browse.', {help: DATA_ENTRY_HELP, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA});
|
|
112
112
|
}
|
|
113
|
-
throw validationError(at, `type "${record.type}" is not supported. Please use read or browse.`, DATA_ENTRY_HELP);
|
|
113
|
+
throw validationError(at, `type "${record.type}" is not supported. Please use read or browse.`, {help: DATA_ENTRY_HELP, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA});
|
|
114
114
|
}
|
|
115
115
|
if (issue.path.includes('resource')) {
|
|
116
116
|
if (record.resource === undefined) {
|
|
117
|
-
throw validationError(at, `resource is required. Please use ${humanList(VALID_LONGFORM_RESOURCES)}.`, DATA_ENTRY_HELP);
|
|
117
|
+
throw validationError(at, `resource is required. Please use ${humanList(VALID_LONGFORM_RESOURCES)}.`, {help: DATA_ENTRY_HELP, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_RESOURCE});
|
|
118
118
|
}
|
|
119
|
-
throw validationError(at, `resource "${record.resource}" is not supported. Please use ${humanList(VALID_LONGFORM_RESOURCES)}.`, DATA_ENTRY_HELP);
|
|
119
|
+
throw validationError(at, `resource "${record.resource}" is not supported. Please use ${humanList(VALID_LONGFORM_RESOURCES)}.`, {help: DATA_ENTRY_HELP, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_RESOURCE});
|
|
120
120
|
}
|
|
121
121
|
if (issue.path.includes('slug') && !record.slug) {
|
|
122
|
-
throw validationError(at, 'slug is required for read data entries.', DATA_ENTRY_HELP);
|
|
122
|
+
throw validationError(at, 'slug is required for read data entries.', {help: DATA_ENTRY_HELP, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA});
|
|
123
123
|
}
|
|
124
|
-
throw toValidationError(result.error, path, value);
|
|
124
|
+
throw toValidationError(result.error, path, value, ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA);
|
|
125
125
|
}
|
|
126
126
|
// Fields set to an explicitly empty value parse to undefined — drop the
|
|
127
127
|
// keys so "unset" means absent in the domain model and serialized output.
|
|
@@ -137,7 +137,7 @@ function parseDataEntry(value: unknown, path: PathSegment[]): DataEntry {
|
|
|
137
137
|
throw validationError(
|
|
138
138
|
formatLocation(path),
|
|
139
139
|
`a data entry must be a shorthand like tag.recipes, or a map with type and resource, but ${describeValue(value)} was provided.`,
|
|
140
|
-
DATA_ENTRY_HELP
|
|
140
|
+
{help: DATA_ENTRY_HELP, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA}
|
|
141
141
|
);
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -151,7 +151,7 @@ function parseRouteData(data: unknown, path: PathSegment[]): RouteData {
|
|
|
151
151
|
throw validationError(
|
|
152
152
|
formatLocation(path),
|
|
153
153
|
`data must be a shorthand like tag.recipes, or a map of named data entries, but ${describeValue(data)} was provided.`,
|
|
154
|
-
DATA_ENTRY_HELP
|
|
154
|
+
{help: DATA_ENTRY_HELP, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA}
|
|
155
155
|
);
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -159,10 +159,10 @@ function parseRouteData(data: unknown, path: PathSegment[]): RouteData {
|
|
|
159
159
|
|
|
160
160
|
for (const key of Object.keys(record)) {
|
|
161
161
|
if (RESERVED_DATA_KEYS.includes(key)) {
|
|
162
|
-
throw validationError(formatLocation([...path, key]), tpl(messages.badDataError, {key}), messages.badDataHelp);
|
|
162
|
+
throw validationError(formatLocation([...path, key]), tpl(messages.badDataError, {key}), {help: messages.badDataHelp, code: ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA});
|
|
163
163
|
}
|
|
164
164
|
if (key === 'author') {
|
|
165
|
-
throw validationError(formatLocation([...path, key]), messages.authorDeprecatedError);
|
|
165
|
+
throw validationError(formatLocation([...path, key]), messages.authorDeprecatedError, {code: ROUTE_SETTINGS_ERROR_CODES.INVALID_DATA});
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -202,8 +202,8 @@ const routeObjectSchema = (path: PathSegment[]) => z.object({
|
|
|
202
202
|
templates
|
|
203
203
|
};
|
|
204
204
|
// Preserve rss only when the author set it explicitly — the domain model
|
|
205
|
-
// mirrors user intent (unset vs true vs false), so
|
|
206
|
-
//
|
|
205
|
+
// mirrors user intent (unset vs true vs false), so serializing never
|
|
206
|
+
// writes back an `rss` key the author never wrote.
|
|
207
207
|
if (val.rss !== undefined) {
|
|
208
208
|
route.rss = val.rss;
|
|
209
209
|
}
|
|
@@ -284,19 +284,19 @@ const RouteSettingsSchema = z.object({
|
|
|
284
284
|
* rejecting :param there would break the former, and the suggested rewrite would
|
|
285
285
|
* silently produce the latter.
|
|
286
286
|
*/
|
|
287
|
-
function validatePath(value: string, path: PathSegment[], example: string, {allowParamNotation = false}: {allowParamNotation?: boolean} = {}): void {
|
|
287
|
+
function validatePath(value: string, path: PathSegment[], example: string, {allowParamNotation = false, code = ROUTE_SETTINGS_ERROR_CODES.INVALID_PATH}: {allowParamNotation?: boolean, code?: RouteSettingsErrorCode} = {}): void {
|
|
288
288
|
const at = formatLocation(path);
|
|
289
289
|
|
|
290
290
|
if (!value.startsWith('/')) {
|
|
291
|
-
throw validationError(at, `"${value}" is missing a leading slash. Please use e.g. ${example}
|
|
291
|
+
throw validationError(at, `"${value}" is missing a leading slash. Please use e.g. ${example}.`, {code});
|
|
292
292
|
}
|
|
293
293
|
if (!value.endsWith('/')) {
|
|
294
|
-
throw validationError(at, `"${value}" is missing a trailing slash. Please use e.g. ${example}
|
|
294
|
+
throw validationError(at, `"${value}" is missing a trailing slash. Please use e.g. ${example}.`, {code});
|
|
295
295
|
}
|
|
296
296
|
if (!allowParamNotation && /\/:\w+/.test(value)) {
|
|
297
297
|
// Suggest the same path in the notation Ghost expects, rather than a
|
|
298
298
|
// generic example the author then has to translate.
|
|
299
|
-
throw validationError(at, `"${value}" uses the :param notation. Please use "${value.replace(/\/:(\w+)/g, '/{$1}')}"
|
|
299
|
+
throw validationError(at, `"${value}" uses the :param notation. Please use "${value.replace(/\/:(\w+)/g, '/{$1}')}".`, {code});
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
@@ -317,7 +317,7 @@ export function parseRouteSettings(raw: unknown, yamlSource: string): RouteSetti
|
|
|
317
317
|
validatePath(path, routeLocation, '/about/', {allowParamNotation: true});
|
|
318
318
|
|
|
319
319
|
if (value === null || value === undefined) {
|
|
320
|
-
throw validationError(formatLocation(routeLocation), 'Please define a template, e.g. /about/: about.');
|
|
320
|
+
throw validationError(formatLocation(routeLocation), 'Please define a template, e.g. /about/: about.', {code: ROUTE_SETTINGS_ERROR_CODES.INVALID_TEMPLATE});
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
if (typeof value === 'string') {
|
|
@@ -332,7 +332,7 @@ export function parseRouteSettings(raw: unknown, yamlSource: string): RouteSetti
|
|
|
332
332
|
|
|
333
333
|
const route = routeResult.data;
|
|
334
334
|
if (route.type === 'template' && (!route.templates || route.templates.length === 0) && !route.data && !(route as Omit<TemplateRoute, 'path'>).contentType) {
|
|
335
|
-
throw validationError(formatLocation(routeLocation), 'Please define a template, e.g. /about/: about.');
|
|
335
|
+
throw validationError(formatLocation(routeLocation), 'Please define a template, e.g. /about/: about.', {code: ROUTE_SETTINGS_ERROR_CODES.INVALID_TEMPLATE});
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
routes.push({...route, path} as Route);
|
|
@@ -352,9 +352,9 @@ export function parseRouteSettings(raw: unknown, yamlSource: string): RouteSetti
|
|
|
352
352
|
|
|
353
353
|
const collection = collectionResult.data;
|
|
354
354
|
if (!collection.permalink) {
|
|
355
|
-
throw validationError(formatLocation(collectionLocation), 'Please define a permalink route, e.g. permalink: /{slug}/.');
|
|
355
|
+
throw validationError(formatLocation(collectionLocation), 'Please define a permalink route, e.g. permalink: /{slug}/.', {code: ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK});
|
|
356
356
|
}
|
|
357
|
-
validatePath(collection.permalink, [...collectionLocation, 'permalink'], '/{slug}/');
|
|
357
|
+
validatePath(collection.permalink, [...collectionLocation, 'permalink'], '/{slug}/', {code: ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK});
|
|
358
358
|
|
|
359
359
|
collections.push({...collection, path});
|
|
360
360
|
}
|
|
@@ -365,12 +365,12 @@ export function parseRouteSettings(raw: unknown, yamlSource: string): RouteSetti
|
|
|
365
365
|
for (const [key, value] of Object.entries(rawTaxonomies)) {
|
|
366
366
|
const taxonomyLocation: PathSegment[] = ['taxonomies', key];
|
|
367
367
|
if (!['tag', 'author'].includes(key)) {
|
|
368
|
-
throw validationError(formatLocation(taxonomyLocation), 'Unknown taxonomy. Please use tag or author.');
|
|
368
|
+
throw validationError(formatLocation(taxonomyLocation), 'Unknown taxonomy. Please use tag or author.', {code: ROUTE_SETTINGS_ERROR_CODES.INVALID_TAXONOMY});
|
|
369
369
|
}
|
|
370
370
|
if (!value) {
|
|
371
|
-
throw validationError(formatLocation(taxonomyLocation), `Please define a taxonomy permalink route, e.g. ${key}: /${key}/{slug}
|
|
371
|
+
throw validationError(formatLocation(taxonomyLocation), `Please define a taxonomy permalink route, e.g. ${key}: /${key}/{slug}/.`, {code: ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK});
|
|
372
372
|
}
|
|
373
|
-
validatePath(value, taxonomyLocation, `/${key}/{slug}
|
|
373
|
+
validatePath(value, taxonomyLocation, `/${key}/{slug}/`, {code: ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK});
|
|
374
374
|
if (key === 'tag') {
|
|
375
375
|
taxonomies.tag = value;
|
|
376
376
|
}
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ROUTE_SETTINGS_ERROR_CODES = void 0;
|
|
6
7
|
exports.formatLocation = formatLocation;
|
|
7
8
|
exports.describeValue = describeValue;
|
|
8
9
|
exports.humanList = humanList;
|
|
@@ -15,6 +16,37 @@ const messages = {
|
|
|
15
16
|
};
|
|
16
17
|
const DOCS_URL = 'https://ghost.org/docs/themes/routing/';
|
|
17
18
|
const MAX_SHOWN_LENGTH = 40;
|
|
19
|
+
/**
|
|
20
|
+
* Stable, machine-readable codes describing the class of a routes.yaml failure.
|
|
21
|
+
*
|
|
22
|
+
* The human-readable message is authored for the person editing the file and is
|
|
23
|
+
* free to change; the code is the contract a caller can branch on. Every
|
|
24
|
+
* validation error carries one — the default is the generic
|
|
25
|
+
* `INVALID_ROUTE_SETTINGS`.
|
|
26
|
+
*
|
|
27
|
+
* Codes name the *kind* of thing that is wrong, independent of which section it
|
|
28
|
+
* was found in, so a caller can branch on the failure class without also
|
|
29
|
+
* knowing whether it came from a route, a collection or a taxonomy:
|
|
30
|
+
*
|
|
31
|
+
* - `INVALID_ROUTE_SETTINGS` — the overall document/section shape is wrong (the
|
|
32
|
+
* schema rejected it); also the default for anything not otherwise classified.
|
|
33
|
+
* - `INVALID_PATH` — a mount path key (a `routes:`/`collections:` key) is malformed.
|
|
34
|
+
* - `INVALID_PERMALINK` — a permalink value is missing or malformed, wherever it
|
|
35
|
+
* appears (a collection `permalink` or a taxonomy permalink).
|
|
36
|
+
* - `INVALID_TAXONOMY` — the taxonomy itself is not one Ghost supports (not tag/author).
|
|
37
|
+
* - `INVALID_TEMPLATE` — a route/collection has no template, data or content type.
|
|
38
|
+
* - `INVALID_RESOURCE` — a data entry names an unsupported resource.
|
|
39
|
+
* - `INVALID_DATA` — a data definition's shape is otherwise invalid.
|
|
40
|
+
*/
|
|
41
|
+
exports.ROUTE_SETTINGS_ERROR_CODES = {
|
|
42
|
+
INVALID_ROUTE_SETTINGS: 'INVALID_ROUTE_SETTINGS',
|
|
43
|
+
INVALID_PATH: 'INVALID_PATH',
|
|
44
|
+
INVALID_PERMALINK: 'INVALID_PERMALINK',
|
|
45
|
+
INVALID_TAXONOMY: 'INVALID_TAXONOMY',
|
|
46
|
+
INVALID_TEMPLATE: 'INVALID_TEMPLATE',
|
|
47
|
+
INVALID_RESOURCE: 'INVALID_RESOURCE',
|
|
48
|
+
INVALID_DATA: 'INVALID_DATA'
|
|
49
|
+
};
|
|
18
50
|
/**
|
|
19
51
|
* Renders the path to the offending value the way an author would find it in
|
|
20
52
|
* their file, e.g. `routes['/about/'].template[1]`.
|
|
@@ -140,6 +172,33 @@ function describeExpectation(path) {
|
|
|
140
172
|
const expectation = lookup(FIELD_EXPECTATIONS, fieldKey(path));
|
|
141
173
|
return expectation ? { subject: String(fieldKey(path)), expectation } : null;
|
|
142
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Codes that follow from the key a schema failure landed on, so a failure the
|
|
177
|
+
* schema catches is classified the same as the equivalent one caught by hand.
|
|
178
|
+
*
|
|
179
|
+
* Without this, which code an author gets depends on how they happened to write
|
|
180
|
+
* the mistake: `permalink: ""` is caught by an explicit check and coded
|
|
181
|
+
* `INVALID_PERMALINK`, while `permalink:` (empty in YAML, so `null`) and
|
|
182
|
+
* `permalink: 42` are rejected by the schema one step earlier and would fall
|
|
183
|
+
* back to the generic code. The empty form is the common one, so the most
|
|
184
|
+
* frequent permalink mistake would be the one carrying the least useful code.
|
|
185
|
+
*/
|
|
186
|
+
const FIELD_CODES = {
|
|
187
|
+
permalink: exports.ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* The code implied by `path`, or undefined when the key implies nothing more
|
|
191
|
+
* specific than "the schema rejected it".
|
|
192
|
+
*/
|
|
193
|
+
function codeForPath(path) {
|
|
194
|
+
// A taxonomy's value *is* its permalink, so the key is the taxonomy name
|
|
195
|
+
// (`taxonomies.tag`) rather than a `permalink` field.
|
|
196
|
+
if (path.length === 2 && path[0] === 'taxonomies') {
|
|
197
|
+
return exports.ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK;
|
|
198
|
+
}
|
|
199
|
+
const key = fieldKey(path);
|
|
200
|
+
return typeof key === 'string' && Object.prototype.hasOwnProperty.call(FIELD_CODES, key) ? FIELD_CODES[key] : undefined;
|
|
201
|
+
}
|
|
143
202
|
function valueAtPath(value, path) {
|
|
144
203
|
return path.reduce((current, segment) => {
|
|
145
204
|
if (current === null || typeof current !== 'object') {
|
|
@@ -148,9 +207,10 @@ function valueAtPath(value, path) {
|
|
|
148
207
|
return current[segment];
|
|
149
208
|
}, value);
|
|
150
209
|
}
|
|
151
|
-
function validationError(at, reason, help) {
|
|
210
|
+
function validationError(at, reason, { help, code = exports.ROUTE_SETTINGS_ERROR_CODES.INVALID_ROUTE_SETTINGS } = {}) {
|
|
152
211
|
return new errors_1.default.ValidationError({
|
|
153
212
|
message: (0, tpl_1.default)(messages.validationError, { at, reason }),
|
|
213
|
+
code,
|
|
154
214
|
...(help ? { help } : {})
|
|
155
215
|
});
|
|
156
216
|
}
|
|
@@ -162,17 +222,21 @@ function validationError(at, reason, help) {
|
|
|
162
222
|
* @param error the schema failure
|
|
163
223
|
* @param basePath path of the value that was handed to the schema
|
|
164
224
|
* @param value that same value, used to describe what the author wrote
|
|
225
|
+
* @param code forces the code; omit to infer it from the failing key, which
|
|
226
|
+
* is what callers covering a whole section (a route, a
|
|
227
|
+
* collection) want — the failure could be about any field in it.
|
|
165
228
|
*/
|
|
166
|
-
function toValidationError(error, basePath = [], value) {
|
|
229
|
+
function toValidationError(error, basePath = [], value, code) {
|
|
167
230
|
const issue = error.issues[0];
|
|
168
231
|
const issuePath = issue.path;
|
|
169
232
|
const path = [...basePath, ...issuePath];
|
|
233
|
+
const resolvedCode = code ?? codeForPath(path) ?? exports.ROUTE_SETTINGS_ERROR_CODES.INVALID_ROUTE_SETTINGS;
|
|
170
234
|
const expectation = describeExpectation(path);
|
|
171
235
|
if (!expectation) {
|
|
172
|
-
return validationError(formatLocation(path), issue.message, DOCS_URL);
|
|
236
|
+
return validationError(formatLocation(path), issue.message, { help: DOCS_URL, code: resolvedCode });
|
|
173
237
|
}
|
|
174
238
|
const provided = valueAtPath(value, issuePath);
|
|
175
239
|
const listProblem = Array.isArray(provided) ? lookup(LIST_ENTRY_PROBLEMS, fieldKey(path)) : undefined;
|
|
176
240
|
const found = listProblem ?? `${describeValue(provided)} was provided`;
|
|
177
|
-
return validationError(formatLocation(path), `${expectation.subject} must be ${expectation.expectation}, but ${found}.`, DOCS_URL);
|
|
241
|
+
return validationError(formatLocation(path), `${expectation.subject} must be ${expectation.expectation}, but ${found}.`, { help: DOCS_URL, code: resolvedCode });
|
|
178
242
|
}
|
|
@@ -11,6 +11,40 @@ const MAX_SHOWN_LENGTH = 40;
|
|
|
11
11
|
|
|
12
12
|
export type PathSegment = string | number;
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Stable, machine-readable codes describing the class of a routes.yaml failure.
|
|
16
|
+
*
|
|
17
|
+
* The human-readable message is authored for the person editing the file and is
|
|
18
|
+
* free to change; the code is the contract a caller can branch on. Every
|
|
19
|
+
* validation error carries one — the default is the generic
|
|
20
|
+
* `INVALID_ROUTE_SETTINGS`.
|
|
21
|
+
*
|
|
22
|
+
* Codes name the *kind* of thing that is wrong, independent of which section it
|
|
23
|
+
* was found in, so a caller can branch on the failure class without also
|
|
24
|
+
* knowing whether it came from a route, a collection or a taxonomy:
|
|
25
|
+
*
|
|
26
|
+
* - `INVALID_ROUTE_SETTINGS` — the overall document/section shape is wrong (the
|
|
27
|
+
* schema rejected it); also the default for anything not otherwise classified.
|
|
28
|
+
* - `INVALID_PATH` — a mount path key (a `routes:`/`collections:` key) is malformed.
|
|
29
|
+
* - `INVALID_PERMALINK` — a permalink value is missing or malformed, wherever it
|
|
30
|
+
* appears (a collection `permalink` or a taxonomy permalink).
|
|
31
|
+
* - `INVALID_TAXONOMY` — the taxonomy itself is not one Ghost supports (not tag/author).
|
|
32
|
+
* - `INVALID_TEMPLATE` — a route/collection has no template, data or content type.
|
|
33
|
+
* - `INVALID_RESOURCE` — a data entry names an unsupported resource.
|
|
34
|
+
* - `INVALID_DATA` — a data definition's shape is otherwise invalid.
|
|
35
|
+
*/
|
|
36
|
+
export const ROUTE_SETTINGS_ERROR_CODES = {
|
|
37
|
+
INVALID_ROUTE_SETTINGS: 'INVALID_ROUTE_SETTINGS',
|
|
38
|
+
INVALID_PATH: 'INVALID_PATH',
|
|
39
|
+
INVALID_PERMALINK: 'INVALID_PERMALINK',
|
|
40
|
+
INVALID_TAXONOMY: 'INVALID_TAXONOMY',
|
|
41
|
+
INVALID_TEMPLATE: 'INVALID_TEMPLATE',
|
|
42
|
+
INVALID_RESOURCE: 'INVALID_RESOURCE',
|
|
43
|
+
INVALID_DATA: 'INVALID_DATA'
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export type RouteSettingsErrorCode = typeof ROUTE_SETTINGS_ERROR_CODES[keyof typeof ROUTE_SETTINGS_ERROR_CODES];
|
|
47
|
+
|
|
14
48
|
/**
|
|
15
49
|
* Renders the path to the offending value the way an author would find it in
|
|
16
50
|
* their file, e.g. `routes['/about/'].template[1]`.
|
|
@@ -153,6 +187,37 @@ function describeExpectation(path: readonly PathSegment[]): {subject: string, ex
|
|
|
153
187
|
return expectation ? {subject: String(fieldKey(path)), expectation} : null;
|
|
154
188
|
}
|
|
155
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Codes that follow from the key a schema failure landed on, so a failure the
|
|
192
|
+
* schema catches is classified the same as the equivalent one caught by hand.
|
|
193
|
+
*
|
|
194
|
+
* Without this, which code an author gets depends on how they happened to write
|
|
195
|
+
* the mistake: `permalink: ""` is caught by an explicit check and coded
|
|
196
|
+
* `INVALID_PERMALINK`, while `permalink:` (empty in YAML, so `null`) and
|
|
197
|
+
* `permalink: 42` are rejected by the schema one step earlier and would fall
|
|
198
|
+
* back to the generic code. The empty form is the common one, so the most
|
|
199
|
+
* frequent permalink mistake would be the one carrying the least useful code.
|
|
200
|
+
*/
|
|
201
|
+
const FIELD_CODES: Record<string, RouteSettingsErrorCode> = {
|
|
202
|
+
permalink: ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The code implied by `path`, or undefined when the key implies nothing more
|
|
207
|
+
* specific than "the schema rejected it".
|
|
208
|
+
*/
|
|
209
|
+
function codeForPath(path: readonly PathSegment[]): RouteSettingsErrorCode | undefined {
|
|
210
|
+
// A taxonomy's value *is* its permalink, so the key is the taxonomy name
|
|
211
|
+
// (`taxonomies.tag`) rather than a `permalink` field.
|
|
212
|
+
if (path.length === 2 && path[0] === 'taxonomies') {
|
|
213
|
+
return ROUTE_SETTINGS_ERROR_CODES.INVALID_PERMALINK;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const key = fieldKey(path);
|
|
217
|
+
|
|
218
|
+
return typeof key === 'string' && Object.prototype.hasOwnProperty.call(FIELD_CODES, key) ? FIELD_CODES[key] : undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
156
221
|
function valueAtPath(value: unknown, path: readonly PathSegment[]): unknown {
|
|
157
222
|
return path.reduce<unknown>((current, segment) => {
|
|
158
223
|
if (current === null || typeof current !== 'object') {
|
|
@@ -162,9 +227,14 @@ function valueAtPath(value: unknown, path: readonly PathSegment[]): unknown {
|
|
|
162
227
|
}, value);
|
|
163
228
|
}
|
|
164
229
|
|
|
165
|
-
export function validationError(
|
|
230
|
+
export function validationError(
|
|
231
|
+
at: string,
|
|
232
|
+
reason: string,
|
|
233
|
+
{help, code = ROUTE_SETTINGS_ERROR_CODES.INVALID_ROUTE_SETTINGS}: {help?: string, code?: RouteSettingsErrorCode} = {}
|
|
234
|
+
): errors.ValidationError {
|
|
166
235
|
return new errors.ValidationError({
|
|
167
236
|
message: tpl(messages.validationError, {at, reason}),
|
|
237
|
+
code,
|
|
168
238
|
...(help ? {help} : {})
|
|
169
239
|
});
|
|
170
240
|
}
|
|
@@ -177,20 +247,25 @@ export function validationError(at: string, reason: string, help?: string): erro
|
|
|
177
247
|
* @param error the schema failure
|
|
178
248
|
* @param basePath path of the value that was handed to the schema
|
|
179
249
|
* @param value that same value, used to describe what the author wrote
|
|
250
|
+
* @param code forces the code; omit to infer it from the failing key, which
|
|
251
|
+
* is what callers covering a whole section (a route, a
|
|
252
|
+
* collection) want — the failure could be about any field in it.
|
|
180
253
|
*/
|
|
181
|
-
export function toValidationError(error: z.ZodError, basePath: readonly PathSegment[] = [], value?: unknown): errors.ValidationError {
|
|
254
|
+
export function toValidationError(error: z.ZodError, basePath: readonly PathSegment[] = [], value?: unknown, code?: RouteSettingsErrorCode): errors.ValidationError {
|
|
182
255
|
const issue = error.issues[0];
|
|
183
256
|
const issuePath = issue.path as PathSegment[];
|
|
184
257
|
const path = [...basePath, ...issuePath];
|
|
185
258
|
|
|
259
|
+
const resolvedCode = code ?? codeForPath(path) ?? ROUTE_SETTINGS_ERROR_CODES.INVALID_ROUTE_SETTINGS;
|
|
260
|
+
|
|
186
261
|
const expectation = describeExpectation(path);
|
|
187
262
|
if (!expectation) {
|
|
188
|
-
return validationError(formatLocation(path), issue.message, DOCS_URL);
|
|
263
|
+
return validationError(formatLocation(path), issue.message, {help: DOCS_URL, code: resolvedCode});
|
|
189
264
|
}
|
|
190
265
|
|
|
191
266
|
const provided = valueAtPath(value, issuePath);
|
|
192
267
|
const listProblem = Array.isArray(provided) ? lookup(LIST_ENTRY_PROBLEMS, fieldKey(path)) : undefined;
|
|
193
268
|
const found = listProblem ?? `${describeValue(provided)} was provided`;
|
|
194
269
|
|
|
195
|
-
return validationError(formatLocation(path), `${expectation.subject} must be ${expectation.expectation}, but ${found}.`, DOCS_URL);
|
|
270
|
+
return validationError(formatLocation(path), `${expectation.subject} must be ${expectation.expectation}, but ${found}.`, {help: DOCS_URL, code: resolvedCode});
|
|
196
271
|
}
|
|
@@ -399,29 +399,33 @@ class PostsStatsService {
|
|
|
399
399
|
|
|
400
400
|
async getGrowthStatsForPost(postId) {
|
|
401
401
|
try {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
402
|
+
// The three aggregates only depend on postId, so run them together:
|
|
403
|
+
// the wait becomes the slowest query instead of the sum of all three.
|
|
404
|
+
const [freeMembers, paidMembers, mrr] = await Promise.all([
|
|
405
|
+
this.knex('members_created_events as mce')
|
|
406
|
+
.countDistinct('mce.member_id as free_members')
|
|
407
|
+
.leftJoin('members_subscription_created_events as msce', function () {
|
|
408
|
+
this.on('mce.member_id', '=', 'msce.member_id')
|
|
409
|
+
.andOn('mce.attribution_id', '=', 'msce.attribution_id');
|
|
410
|
+
})
|
|
411
|
+
.where('mce.attribution_id', postId)
|
|
412
|
+
.whereIn('mce.attribution_type', ['post', 'page'])
|
|
413
|
+
.where('msce.id', null),
|
|
414
|
+
|
|
415
|
+
this.knex('members_subscription_created_events as msce')
|
|
416
|
+
.countDistinct('msce.member_id as paid_members')
|
|
417
|
+
.where('msce.attribution_id', postId)
|
|
418
|
+
.whereIn('msce.attribution_type', ['post', 'page']),
|
|
419
|
+
|
|
420
|
+
this.knex('members_subscription_created_events as msce')
|
|
421
|
+
.sum('mpse.mrr_delta as mrr')
|
|
422
|
+
.join('members_paid_subscription_events as mpse', function () {
|
|
423
|
+
this.on('mpse.subscription_id', '=', 'msce.subscription_id');
|
|
424
|
+
this.andOn('mpse.member_id', '=', 'msce.member_id');
|
|
425
|
+
})
|
|
426
|
+
.where('msce.attribution_id', postId)
|
|
427
|
+
.whereIn('msce.attribution_type', ['post', 'page'])
|
|
428
|
+
]);
|
|
425
429
|
|
|
426
430
|
return {
|
|
427
431
|
data: [
|
|
@@ -50,7 +50,7 @@ module.exports = class ChargeRefundedEventService {
|
|
|
50
50
|
* @private
|
|
51
51
|
*/
|
|
52
52
|
async handleGiftRefundEvent(paymentIntentId) {
|
|
53
|
-
const refunded = await this.deps.giftService.
|
|
53
|
+
const refunded = await this.deps.giftService.handlePaymentRefund({paymentIntentId});
|
|
54
54
|
|
|
55
55
|
if (!refunded) {
|
|
56
56
|
logging.info(`charge.refunded: no gift found for payment_intent ${paymentIntentId}, skipping`);
|