ghost 6.53.0 → 6.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ghost-storage-base-3.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
- package/components/tryghost-adapter-base-scheduling-0.1.1.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.1.tgz +0 -0
- package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-i18n-0.0.0.tgz +0 -0
- package/components/tryghost-kg-converters-1.2.3.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
- package/core/boot.js +9 -6
- package/core/built/admin/assets/Combination-CJG7Flp-.js +41 -0
- package/core/built/admin/assets/Growth-CSaHoSVD.js +3 -0
- package/core/built/admin/assets/Newsletters-C5cs6FGo.js +1 -0
- package/core/built/admin/assets/Overview-j4NaeUNm.js +1 -0
- package/core/built/admin/assets/{PolarAngleAxis-pciQ0MOy.js → PolarAngleAxis-BGjf7sBu.js} +1 -1
- package/core/built/admin/assets/{RadialBarChart-DWmw2PcX.js → RadialBarChart-zeFf9mBz.js} +1 -1
- package/core/built/admin/assets/Web-_fD935OX.js +2 -0
- package/core/built/admin/assets/account-migration-DLvVA5yq.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-BPZG37DJ.js → account-migration-DHbZcxGM.js} +13 -13
- package/core/built/admin/assets/activitypub/activitypub.js +7 -7
- package/core/built/admin/assets/activitypub/{avatar-ZYco0ITb.js → avatar-B3DEDB1l.js} +10 -10
- package/core/built/admin/assets/activitypub/{avatar-flipboard-D541lc4F.js → avatar-flipboard-Cqrg0XMx.js} +3 -3
- package/core/built/admin/assets/activitypub/{bluesky-sharing-NOHUtHjl.js → bluesky-sharing-zfxUey_L.js} +38 -37
- package/core/built/admin/assets/activitypub/{chunk-KS7C4IRE-B-GOFDPZ.js → chunk-KS7C4IRE-D4HEcEwi.js} +716 -710
- package/core/built/admin/assets/activitypub/{content-formatters-bwrp4iEO.js → content-formatters-BpQARh28.js} +229 -181
- package/core/built/admin/assets/activitypub/{copy-C7m7ovHw.js → copy-D9UWIAan.js} +2 -2
- package/core/built/admin/assets/activitypub/{dist-B5ira32t.js → dist-Ba_QxHHl.js} +5 -5
- package/core/built/admin/assets/activitypub/{domain-iqE4YXwD.js → domain-CmwSZLHK.js} +33 -33
- package/core/built/admin/assets/activitypub/{edit-profile-B8MFYh28.js → edit-profile-CXdwNmVG.js} +46 -45
- package/core/built/admin/assets/activitypub/{explore-BbdKodp3.js → explore-DfbjzGM1.js} +20 -20
- package/core/built/admin/assets/activitypub/{feed-Cl3A5owG.js → feed-DSEBJOqf.js} +15 -15
- package/core/built/admin/assets/activitypub/{field-DL6WyR-c.js → field-CY7XVcx6.js} +13 -13
- package/core/built/admin/assets/activitypub/{hash-6G3DzwL9.js → hash-BC9Q0N4c.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-YmhuBice.js → inbox-DQ4ZnC1m.js} +2 -2
- package/core/built/admin/assets/activitypub/inbox-DtQ0kytT.js +1221 -0
- package/core/built/admin/assets/activitypub/{label-DGbLjDvE.js → label-Dw96GQ7u.js} +6 -6
- package/core/built/admin/assets/activitypub/layout-IdVq3lRy.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-qnBtk1zS.js → moderation-Dg_-I-8C.js} +32 -31
- package/core/built/admin/assets/activitypub/{note-C7ZRoSJv.js → note-Cy1-uKMY.js} +21 -21
- package/core/built/admin/assets/activitypub/{notifications-DiEA4b1o.js → notifications-B_fvoLIM.js} +17 -17
- package/core/built/admin/assets/activitypub/{onboarding-_JVgEwgV.js → onboarding-BFDkzeR2.js} +2 -2
- package/core/built/admin/assets/activitypub/{onboarding-DG4zu3qM.js → onboarding-D6rcBdPe.js} +5 -5
- package/core/built/admin/assets/activitypub/{preferences-B3FtocnP.js → preferences-ByJseZp6.js} +1607 -1606
- package/core/built/admin/assets/activitypub/{profile-D8ZJfYXB.js → profile-BFtbnIjz.js} +264 -263
- package/core/built/admin/assets/activitypub/{reply-C1vggenw.js → reply-CpdMeAO5.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-BOii-V9n.js → routes-Dh3xXfce.js} +1311 -1295
- package/core/built/admin/assets/activitypub/{separator-1gNokN5S.js → separator-BI1FLsYM.js} +4 -4
- package/core/built/admin/assets/activitypub/{settings--9Sb0nbl.js → settings-DTgqJPxM.js} +14 -14
- package/core/built/admin/assets/activitypub/{site-fkMmqv0V.js → site-CdL9ZLqm.js} +10 -10
- package/core/built/admin/assets/activitypub/{step-1-QhivSMw2.js → step-1-DIDg4aHb.js} +15 -15
- package/core/built/admin/assets/activitypub/{step-2-pMk9YwqD.js → step-2-BfGe8BYt.js} +7 -7
- package/core/built/admin/assets/activitypub/{step-3-DvmVC1fo.js → step-3-mE9spAqw.js} +12 -12
- package/core/built/admin/assets/activitypub/{tabs-Dc0FEEYD.js → tabs-ogqwoBlb.js} +241 -237
- package/core/built/admin/assets/activitypub/{topic-filter-CuDyM5bD.js → topic-filter-DLHBZT93.js} +4 -4
- package/core/built/admin/assets/activitypub/use-activity-pub-queries-VzbN99u_.js +1957 -0
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-Drx1kdAK.js → use-navigate-with-base-path-CAwXTmqH.js} +165 -175
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-BHkCHcW5.js → use-reply-chain-data-CxonteFD.js} +8 -8
- package/core/built/admin/assets/activitypub/users-BLeiOTHc.js +1464 -0
- package/core/built/admin/assets/activitypub/{x-BHCEe-c2.js → x-C8tAnXly.js} +2 -2
- package/core/built/admin/assets/{audience-adiFehTD.js → audience-BKRdsFmy.js} +1 -1
- package/core/built/admin/assets/{automated-emails-UWM02CMr.js → automated-emails-Cn87L5Md.js} +1 -1
- package/core/built/admin/assets/automations-B7-y3dXt.js +1 -0
- package/core/built/admin/assets/automations-BP_aHamJ.js +1 -0
- package/core/built/admin/assets/{avatar-flipboard-Lz3F9fNT.js → avatar-flipboard-C6Kj3Awu.js} +1 -1
- package/core/built/admin/assets/bluesky-sharing-Bh36XPh4.js +1 -0
- package/core/built/admin/assets/box-BWbhXh21.js +1 -0
- package/core/built/admin/assets/breadcrumb-D34YWnaw.js +1 -0
- package/core/built/admin/assets/card-DGoDJzrn.js +1 -0
- package/core/built/admin/assets/{chart-ChpW-NzQ.js → chart-BuRT2Y6p.js} +14 -14
- package/core/built/admin/assets/chart-helpers-DbklaJw9.js +1 -0
- package/core/built/admin/assets/checkbox-DA4-1UiI.js +1 -0
- package/core/built/admin/assets/chunk.524.45e0875f41b71e46e849.js +25 -0
- package/core/built/admin/assets/chunk.582.51defd4b009f0de8d4d5.js +27 -0
- package/core/built/admin/assets/{chunk.831.0703a12e49f51cec3f3a.js → chunk.589.096f4d0bb775e5229aa9.js} +78 -85
- package/core/built/admin/assets/{chunk.432.c2caa0b20f03e71d1a35.js → chunk.962.b73442b07be930bb5d85.js} +2216 -3188
- package/core/built/admin/assets/{chunk.432.c2caa0b20f03e71d1a35.js.LICENSE.txt → chunk.962.b73442b07be930bb5d85.js.LICENSE.txt} +0 -63
- package/core/built/admin/assets/code-editor-view-BKBrkffZ.js +1 -0
- package/core/built/admin/assets/comments-CzVNIT8b.js +1 -0
- package/core/built/admin/assets/container-BYz2Ed39.js +1 -0
- package/core/built/admin/assets/data-list-16rkJYYi.js +1 -0
- package/core/built/admin/assets/date-range-select-B5vSsrpx.js +1 -0
- package/core/built/admin/assets/disabled-sources-indicator-NkOIoMue.js +1 -0
- package/core/built/admin/assets/{dist-CsekE4FL.js → dist-CQJY6SVY.js} +1 -1
- package/core/built/admin/assets/dist-DGzPJ_bO.js +4 -0
- package/core/built/admin/assets/{dist-BbGrW05X.js → dist-DyViJwIy.js} +1 -1
- package/core/built/admin/assets/dist-Ndy-PuGN.js +1 -0
- package/core/built/admin/assets/dist-P_ohUlTi.js +1 -0
- package/core/built/admin/assets/domain-B7_n5nfx.js +1 -0
- package/core/built/admin/assets/dropzone-DcuJEEUf.js +5 -0
- package/core/built/admin/assets/edit-profile-rvDGrS8J.js +1 -0
- package/core/built/admin/assets/editor-BCiIyFjm.js +8 -0
- package/core/built/admin/assets/empty-indicator-oX1u1Pkn.js +1 -0
- package/core/built/admin/assets/esm-CFX93H5M.js +2 -0
- package/core/built/admin/assets/explore-BZAZOg5X.js +1 -0
- package/core/built/admin/assets/feed-CFV8xDw9.js +1 -0
- package/core/built/admin/assets/field-BW-M2Zwp.js +1 -0
- package/core/built/admin/assets/{filter-sources-v39ihhhV.js → filter-sources-Fx8wiYYw.js} +1 -1
- package/core/built/admin/assets/filters-CGX_pf2N.js +1 -0
- package/core/built/admin/assets/filters-DtcXMZDU.js +1 -0
- package/core/built/admin/assets/framework-provider-CmtnPXIT.js +4 -0
- package/core/built/admin/assets/{ghost-b57f077997b739a8ae9a70772dc016cb.js → ghost-b1dce91ad3232cc259ee5fbbf9526243.js} +294 -290
- package/core/built/admin/assets/ghost-dark-c8d0ab7f515b2010c173e23e9b32ea8a.css +1 -0
- package/core/built/admin/assets/gift-link-modal-Bk8qpnMw.js +1 -0
- package/core/built/admin/assets/{gift-links-CY3GwiVi.js → gift-links-nBxJjZiQ.js} +1 -1
- package/core/built/admin/assets/growth-Ck2s3JjR.js +1 -0
- package/core/built/admin/assets/hooks-BApwwBE0.js +2 -0
- package/core/built/admin/assets/{inbox-TEISAmth.js → inbox-BRp1_aaC.js} +35 -35
- package/core/built/admin/assets/index-BBf502Wg.js +69 -0
- package/core/built/admin/assets/index-BCCZKxP2.css +1 -0
- package/core/built/admin/assets/inline-EMAm3okk.js +1 -0
- package/core/built/admin/assets/input-group-DjuYnNCd.js +1 -0
- package/core/built/admin/assets/{react-dom-C6aoWSfl.js → jsx-runtime-DX3WBWVO.js} +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +5 -2
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +2 -2
- package/core/built/admin/assets/{koenig-lexical-Di7b48ED.js → koenig-lexical-CM7UFNdL.js} +2 -2
- package/core/built/admin/assets/kpi-card-BrGH6FsT.js +1 -0
- package/core/built/admin/assets/kpi-card-CIGyijjx.js +1 -0
- package/core/built/admin/assets/kpi-helpers-_HRRsklE.js +1 -0
- package/core/built/admin/assets/kpi-tabs-BSvh9eoq.js +1 -0
- package/core/built/admin/assets/label-BOR9XdKl.js +1 -0
- package/core/built/admin/assets/label-picker-BdNAWuUX.js +1 -0
- package/core/built/admin/assets/{labels-GbxlXWtf.js → labels-Cyayfd-e.js} +1 -1
- package/core/built/admin/assets/layout-Bw4GnxXp.js +1 -0
- package/core/built/admin/assets/limit-service-9fwD33lU.js +20 -0
- package/core/built/admin/assets/link-helpers-tOlxmuH5.js +1 -0
- package/core/built/admin/assets/list-page-B6ANtzkd.js +1 -0
- package/core/built/admin/assets/mailchimp-Ds_NQ04w.svg +9 -0
- package/core/built/admin/assets/member-custom-fields-DvFyamLu.js +1 -0
- package/core/built/admin/assets/member-detail-Cg2vTqvJ.js +1 -0
- package/core/built/admin/assets/members-DsPxYAVm.js +10 -0
- package/core/built/admin/assets/modals-Bf_fyvHZ.js +31 -0
- package/core/built/admin/assets/moderation-Dlp7M5sa.js +1 -0
- package/core/built/admin/assets/multi-select-combobox-CH1o8XXQ.js +1 -0
- package/core/built/admin/assets/newsletter-CQ5CAYUI.js +1 -0
- package/core/built/admin/assets/{newsletters-DZm3yLj8.js → newsletters-D1jbtV-h.js} +1 -1
- package/core/built/admin/assets/note-BrAZ7Z0o.js +1 -0
- package/core/built/admin/assets/notifications-BnbHLOPC.js +1 -0
- package/core/built/admin/assets/nql-lang-B7faUf8D.js +19 -0
- package/core/built/admin/assets/{offers-D11QtPXX.js → offers-CrY6nSRR.js} +1 -1
- package/core/built/admin/assets/onboarding-route-eU146kuA.js +1 -0
- package/core/built/admin/assets/overview-BpbaK-xu.js +1 -0
- package/core/built/admin/assets/page-header-C0jVxtS8.js +1 -0
- package/core/built/admin/assets/{pages-BnObVRrf.js → pages-BbNWFcGj.js} +1 -1
- package/core/built/admin/assets/pintura-CXOmq-5H.svg +15 -0
- package/core/built/admin/assets/post-analytics-Iw7dk1mT.js +1 -0
- package/core/built/admin/assets/post-analytics-header-D9pJ4iNm.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-DSfBCtnj.js → post-analytics-provider-BDqcfBor.js} +1 -1
- package/core/built/admin/assets/post-share-modal-DBfKl2GZ.js +1 -0
- package/core/built/admin/assets/{posts-CwH8VJvB.js → posts-EWpZIjkr.js} +1 -1
- package/core/built/admin/assets/preferences-D4EANGoc.js +5 -0
- package/core/built/admin/assets/profile-dJ44Bn_W.js +1 -0
- package/core/built/admin/assets/{referrers-nxR52jPI.js → referrers-BCfFYSHc.js} +1 -1
- package/core/built/admin/assets/router-provider-C1R4AZ2o.js +1 -0
- package/core/built/admin/assets/select--2uVN9_-.js +1 -0
- package/core/built/admin/assets/settings-Bz3RZRmE.js +1 -0
- package/core/built/admin/assets/settings-SD9bxIpt.js +4 -0
- package/core/built/admin/assets/share-modal-CkdTTMJd.js +1 -0
- package/core/built/admin/assets/social-urls-BvFZwYcE.js +8 -0
- package/core/built/admin/assets/sort-button-DZp9Y2r1.js +1 -0
- package/core/built/admin/assets/source-icon-CHr9qJ2Y.js +1 -0
- package/core/built/admin/assets/stack-xiN0eQ6Z.js +1 -0
- package/core/built/admin/assets/stats-DGoXZxWE.js +1 -0
- package/core/built/admin/assets/stats-view-D2-1L0ms.js +1 -0
- package/core/built/admin/assets/step-1-RdahbwyG.js +1 -0
- package/core/built/admin/assets/step-2-D83PeRwd.js +1 -0
- package/core/built/admin/assets/step-3-p_Op4wv4.js +2 -0
- package/core/built/admin/assets/switch-qp518gFQ.js +1 -0
- package/core/built/admin/assets/table-B8LgTQJZ.js +1 -0
- package/core/built/admin/assets/tabs-D3ZOYkM7.js +1 -0
- package/core/built/admin/assets/tags-B4l891z2.js +1 -0
- package/core/built/admin/assets/text-DwtjwGkh.js +1 -0
- package/core/built/admin/assets/textarea-CTlb3Uu6.js +1 -0
- package/core/built/admin/assets/{tiers-EghDzCKc.js → tiers-BgYLWRJv.js} +1 -1
- package/core/built/admin/assets/toggle-group-DNXDkAwf.js +1 -0
- package/core/built/admin/assets/topic-filter-DZGjEcz3.js +1 -0
- package/core/built/admin/assets/{use-analytics-data-0l_OHtnR.js → use-analytics-data-CaT8RirX.js} +1 -1
- package/core/built/admin/assets/use-filter-params-DeQ8sX4N.js +1 -0
- package/core/built/admin/assets/use-growth-stats-9Db2CuJe.js +1 -0
- package/core/built/admin/assets/{use-post-referrers-DwI31Ril.js → use-post-referrers-BV54YcQS.js} +1 -1
- package/core/built/admin/assets/use-reply-chain-data-DuYZGDan.js +1 -0
- package/core/built/admin/assets/use-simple-pagination-8fTua9Nt.js +1 -0
- package/core/built/admin/assets/{use-tinybird-query-DvZBBMG7.js → use-tinybird-query-Dz156HvZ.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-CQtebkvU.js → use-tinybird-token-C9RUdL_n.js} +1 -1
- package/core/built/admin/assets/{users-Pc8IRjk9.js → users-CcSCQFcQ.js} +1 -1
- package/core/built/admin/assets/virtual-list-BCvP0YF-.js +1 -0
- package/core/built/admin/assets/web-DTZnnUGr.js +1 -0
- package/core/built/admin/index.html +18 -22
- package/core/frontend/apps/private-blogging/index.js +1 -1
- package/core/frontend/apps/private-blogging/lib/middleware.js +1 -1
- package/core/frontend/helpers/split.js +2 -1
- package/core/frontend/meta/asset-url.js +1 -1
- package/core/frontend/meta/author-image.js +1 -1
- package/core/frontend/meta/blog-logo.js +1 -1
- package/core/frontend/meta/canonical-url.js +1 -1
- package/core/frontend/meta/cover-image.js +1 -1
- package/core/frontend/meta/get-meta.js +1 -1
- package/core/frontend/meta/og-image.js +1 -1
- package/core/frontend/meta/paginated-url.js +1 -1
- package/core/frontend/meta/twitter-image.js +1 -1
- package/core/frontend/meta/url.js +1 -1
- package/core/frontend/services/assets-minification/admin-auth-assets.js +1 -1
- package/core/frontend/services/llms/handler.js +1 -1
- package/core/frontend/services/proxy.js +31 -2
- package/core/frontend/services/routing/collection-router.js +1 -1
- package/core/frontend/services/routing/controllers/email-post.js +1 -1
- package/core/frontend/services/routing/controllers/entry/markdown.js +1 -1
- package/core/frontend/services/routing/controllers/entry/markdown.ts +1 -1
- package/core/frontend/services/routing/controllers/entry.js +1 -1
- package/core/frontend/services/routing/controllers/entry.ts +1 -1
- package/core/frontend/services/routing/controllers/previews.js +6 -7
- package/core/frontend/services/routing/controllers/unsubscribe.js +7 -3
- package/core/frontend/services/routing/email-router.js +1 -1
- package/core/frontend/services/routing/events.js +12 -0
- package/core/frontend/services/routing/middleware/page-param.js +1 -1
- package/core/frontend/services/routing/parent-router.js +1 -1
- package/core/frontend/services/routing/preview-router.js +1 -1
- package/core/frontend/services/routing/router-manager.js +4 -8
- package/core/frontend/services/routing/rss-router.js +1 -1
- package/core/frontend/services/routing/static-pages-router.js +1 -1
- package/core/frontend/services/routing/static-routes-router.js +1 -1
- package/core/frontend/services/routing/taxonomy-router.js +1 -1
- package/core/frontend/services/rss/generate-feed.js +1 -1
- package/core/frontend/services/sitemap/base-site-map-generator.js +23 -4
- package/core/frontend/services/sitemap/site-map-index-generator.js +1 -1
- package/core/frontend/services/sitemap/site-map-manager.js +14 -8
- package/core/frontend/services/sitemap/utils.js +1 -1
- package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +1 -1
- package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +1 -1
- package/core/frontend/utils/images.js +1 -1
- package/core/frontend/web/middleware/admin-toolbar.js +1 -1
- package/core/frontend/web/middleware/handle-image-sizes.js +2 -2
- package/core/frontend/web/middleware/redirect-ghost-to-admin.js +1 -1
- package/core/frontend/web/routers/serve-favicon.js +1 -1
- package/core/frontend/web/routers/serve-public-file.js +1 -1
- package/core/frontend/web/site.js +5 -5
- package/core/server/adapters/cache/AdapterCacheMemoryTTL.js +2 -2
- package/core/server/adapters/cache/MemoryCache.js +2 -2
- package/core/server/adapters/cache/Redis.js +1 -1
- package/core/server/adapters/cache/index.js +1 -1
- package/core/server/adapters/lib/redis/AdapterCacheRedis.js +2 -2
- package/core/server/adapters/route-settings/FileStore.js +2 -8
- package/core/server/adapters/route-settings/FileStore.ts +1 -6
- package/core/server/adapters/route-settings/S3RouteSettingsStore.js +206 -0
- package/core/server/adapters/route-settings/S3RouteSettingsStore.ts +199 -0
- package/core/server/adapters/route-settings/utils.js +13 -0
- package/core/server/adapters/route-settings/utils.ts +7 -0
- package/core/server/adapters/scheduling/deps.d.ts +0 -2
- package/core/server/adapters/scheduling/error-capture.js +76 -0
- package/core/server/adapters/scheduling/error-capture.ts +91 -0
- package/core/server/adapters/scheduling/utils.js +18 -34
- package/core/server/adapters/scheduling/utils.ts +36 -0
- package/core/server/adapters/storage/LocalFilesStorage.js +1 -1
- package/core/server/adapters/storage/LocalImagesStorage.js +1 -1
- package/core/server/adapters/storage/LocalMediaStorage.js +1 -1
- package/core/server/adapters/storage/LocalStorageBase.js +3 -3
- package/core/server/adapters/storage/S3Storage.js +2 -2
- package/core/server/adapters/storage/S3Storage.ts +1 -1
- package/core/server/adapters/storage/utils.js +1 -1
- package/core/server/api/endpoints/authentication.js +1 -1
- package/core/server/api/endpoints/featurebase.js +1 -1
- package/core/server/api/endpoints/files.js +2 -2
- package/core/server/api/endpoints/images.js +2 -2
- package/core/server/api/endpoints/libraries.d.ts +0 -1
- package/core/server/api/endpoints/media.js +8 -5
- package/core/server/api/endpoints/member-custom-fields.js +4 -1
- package/core/server/api/endpoints/member-custom-fields.ts +4 -1
- package/core/server/api/endpoints/posts.js +17 -1
- package/core/server/api/endpoints/search-index-public.js +4 -3
- package/core/server/api/endpoints/search-index.js +5 -4
- package/core/server/api/endpoints/settings-public.js +1 -1
- package/core/server/api/endpoints/settings.js +3 -1
- package/core/server/api/endpoints/utils/serializers/input/utils/url.js +9 -1
- package/core/server/api/endpoints/utils/serializers/output/default.js +7 -0
- package/core/server/api/endpoints/utils/serializers/output/files.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/media.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.js +7 -4
- package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.ts +8 -4
- package/core/server/api/endpoints/utils/serializers/output/members.js +16 -26
- package/core/server/api/endpoints/utils/serializers/output/pages.js +7 -0
- package/core/server/api/endpoints/utils/serializers/output/posts.js +13 -0
- package/core/server/api/endpoints/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/endpoints/utils/validators/input/members.js +2 -52
- package/core/server/data/db/backup.js +1 -1
- package/core/server/data/importer/handlers/image.js +3 -3
- package/core/server/data/importer/import-manager.js +6 -4
- package/core/server/data/importer/importers/content-file-importer.js +2 -2
- package/core/server/data/migrations/utils/schema.js +4 -2
- package/core/server/data/migrations/versions/6.54/2026-07-19-16-59-00-add-content-import-permission.js +10 -0
- package/core/server/data/migrations/versions/6.54/2026-07-20-13-59-21-drop-email-tracked-sent-count-from-automation-action-revisions.js +7 -0
- package/core/server/data/migrations/versions/6.54/2026-07-21-01-58-20-add-automation-run-steps-poller-index.js +3 -0
- package/core/server/data/migrations/versions/6.54/2026-07-21-21-08-15-add-automated-email-recipients-mailgun-message-id-index.js +3 -0
- package/core/server/data/migrations/versions/6.54/2026-07-22-10-41-08-add-click-tracking-to-automated-email-recipients.js +13 -0
- package/core/server/data/migrations/versions/6.54/2026-07-22-15-10-30-add-email-clicked-count-to-automation-action-revisions.js +7 -0
- package/core/server/data/migrations/versions/6.54/2026-07-23-10-24-18-add-automation-action-revision-to-redirects.js +9 -0
- package/core/server/data/schema/commands.js +37 -3
- package/core/server/data/schema/fixtures/fixtures.json +7 -0
- package/core/server/data/schema/lib/default-index-name.js +20 -0
- package/core/server/data/schema/schema.js +40 -17
- package/core/server/data/seeders/data-generator.js +4 -2
- package/core/server/lib/image/image-size.js +5 -7
- package/core/server/lib/image/image-utils.js +2 -2
- package/core/server/lib/image/index.js +4 -4
- package/core/server/lib/lexical.js +3 -3
- package/core/server/lib/mobiledoc.js +2 -2
- package/core/server/lib/validate-password.js +1 -1
- package/core/server/models/action.js +9 -0
- package/core/server/models/base/plugins/generate-slug.js +1 -1
- package/core/server/models/collection.js +1 -1
- package/core/server/models/custom-theme-setting.js +1 -1
- package/core/server/models/member.js +73 -0
- package/core/server/models/newsletter.js +1 -1
- package/core/server/models/post.js +19 -5
- package/core/server/models/posts-meta.js +1 -1
- package/core/server/models/redirect.js +1 -1
- package/core/server/models/settings.js +1 -1
- package/core/server/models/snippet.js +1 -1
- package/core/server/models/tag.js +1 -1
- package/core/server/models/user.js +1 -1
- package/core/server/models/welcome-email-automated-email.js +1 -1
- package/core/server/services/activitypub/activity-pub-service-wrapper.js +1 -1
- package/core/server/services/adapter-manager/adapter-manager.js +5 -1
- package/core/server/services/adapter-manager/adapter-manager.ts +7 -2
- package/core/server/services/adapter-manager/index.js +18 -15
- package/core/server/services/adapter-manager/index.ts +23 -17
- package/core/server/services/adapter-manager/utils.js +124 -14
- package/core/server/services/adapter-manager/utils.ts +130 -14
- package/core/server/services/api-version-compatibility/mw-version-rewrites.js +1 -1
- package/core/server/services/audience-feedback/index.js +1 -1
- package/core/server/services/auth/passwordreset.js +1 -1
- package/core/server/services/auth/session/express-session.js +1 -1
- package/core/server/services/auth/session/index.js +2 -2
- package/core/server/services/auth/session/session-service.js +1 -1
- package/core/server/services/automations/automations-api.js +2 -19
- package/core/server/services/automations/automations-api.ts +2 -31
- package/core/server/services/automations/automations-repository.ts +15 -0
- package/core/server/services/automations/database-automations-repository.js +30 -2
- package/core/server/services/automations/database-automations-repository.ts +33 -2
- package/core/server/services/automations/poll.js +16 -12
- package/core/server/services/automations/poll.ts +17 -15
- package/core/server/services/automations/service.js +1 -1
- package/core/server/services/automations/service.ts +3 -13
- package/core/server/services/automations/welcome-email-automation-poll.js +2 -1
- package/core/server/services/comments/index.js +1 -1
- package/core/server/services/custom-redirects/index.js +2 -2
- package/core/server/services/email-analytics/email-analytics-service-wrapper.js +14 -13
- package/core/server/services/email-analytics/email-analytics-service.js +1 -1
- package/core/server/services/email-analytics/events/start-email-analytics-job-event.js +11 -15
- package/core/server/services/email-analytics/events/start-email-analytics-job-event.ts +15 -0
- package/core/server/services/email-analytics/index.js +2 -3
- package/core/server/services/email-analytics/index.ts +1 -2
- package/core/server/services/email-analytics/jobs/email-analytics-job-scheduler.js +1 -6
- package/core/server/services/email-analytics/jobs/email-analytics-job-scheduler.ts +0 -9
- package/core/server/services/email-analytics/jobs/fetch-latest/index.js +1 -1
- package/core/server/services/email-analytics/jobs/index.js +0 -2
- package/core/server/services/email-service/email-service-wrapper.js +1 -1
- package/core/server/services/email-service/email-service.js +20 -4
- package/core/server/services/explore-ping/explore-ping-service.js +24 -39
- package/core/server/services/explore-ping/explore-ping-service.ts +198 -0
- package/core/server/services/explore-ping/index.js +11 -11
- package/core/server/services/explore-ping/index.ts +42 -0
- package/core/server/services/gifts/gift-reminder-scheduler.js +1 -1
- package/core/server/services/gifts/gift-reminder-scheduler.ts +1 -1
- package/core/server/services/gifts/gift-service-wrapper.js +1 -1
- package/core/server/services/identity-tokens/identity-token-service-wrapper.js +1 -1
- package/core/server/services/indexnow-ping/constants.js +30 -0
- package/core/server/services/indexnow-ping/constants.ts +32 -0
- package/core/server/services/indexnow-ping/index.js +29 -253
- package/core/server/services/indexnow-ping/index.ts +37 -0
- package/core/server/services/indexnow-ping/indexnow-ping-service.js +229 -0
- package/core/server/services/indexnow-ping/indexnow-ping-service.ts +319 -0
- package/core/server/services/invites/index.js +1 -1
- package/core/server/services/lib/mailgun-client.js +3 -3
- package/core/server/services/link-redirection/index.js +2 -2
- package/core/server/services/link-redirection/link-redirect-repository.js +1 -1
- package/core/server/services/link-redirection/link-redirect.js +12 -13
- package/core/server/services/link-redirection/link-redirect.ts +22 -0
- package/core/server/services/link-redirection/link-redirects-service.js +1 -1
- package/core/server/services/link-tracking/full-post-link.js +2 -2
- package/core/server/services/link-tracking/index.js +1 -1
- package/core/server/services/mail/ghost-mailer.js +1 -1
- package/core/server/services/mail/index.js +1 -1
- package/core/server/services/media-inliner/external-media-inliner.js +2 -2
- package/core/server/services/media-inliner/service.js +4 -3
- package/core/server/services/member-attribution/index.js +1 -1
- package/core/server/services/member-welcome-emails/service.js +1 -1
- package/core/server/services/members/api.js +8 -2
- package/core/server/services/members/csv/index.js +10 -0
- package/core/server/services/members/csv/index.ts +3 -0
- package/core/server/services/members/csv/parse.js +94 -0
- package/core/server/services/members/csv/parse.ts +111 -0
- package/core/server/services/members/csv/types.js +2 -0
- package/core/server/services/members/csv/types.ts +39 -0
- package/core/server/services/members/csv/unparse.js +69 -0
- package/core/server/services/members/csv/unparse.ts +79 -0
- package/core/server/services/members/exporter/query.js +35 -8
- package/core/server/services/members/importer/config.js +35 -0
- package/core/server/services/members/importer/config.ts +39 -0
- package/core/server/services/members/importer/members-csv-importer.js +31 -13
- package/core/server/services/members/members-api/members-api.js +4 -2
- package/core/server/services/members/members-api/repositories/event-repository.js +13 -2
- package/core/server/services/members/members-api/services/member-bread-service.js +42 -19
- package/core/server/services/members/middleware.js +1 -1
- package/core/server/services/members/service.js +15 -2
- package/core/server/services/members/stripe-connect.js +1 -1
- package/core/server/services/members-custom-fields/actions.js +3 -0
- package/core/server/services/members-custom-fields/actions.ts +6 -2
- package/core/server/services/members-custom-fields/config.js +34 -0
- package/core/server/services/members-custom-fields/config.ts +38 -0
- package/core/server/services/members-custom-fields/definitions-service.js +156 -21
- package/core/server/services/members-custom-fields/definitions-service.ts +170 -25
- package/core/server/services/members-custom-fields/index.js +16 -3
- package/core/server/services/members-custom-fields/index.ts +17 -3
- package/core/server/services/members-custom-fields/values-service.js +56 -10
- package/core/server/services/members-custom-fields/values-service.ts +62 -10
- package/core/server/services/mentions/mention-sending-service.js +22 -2
- package/core/server/services/mentions/service.js +21 -5
- package/core/server/services/newsletters/index.js +1 -1
- package/core/server/services/oembed/oembed-service.js +122 -33
- package/core/server/services/oembed/service.js +4 -2
- package/core/server/services/offers/service.js +1 -1
- package/core/server/services/post-scheduling/index.js +4 -3
- package/core/server/services/post-scheduling/index.ts +4 -3
- package/core/server/services/post-scheduling/post-scheduling.js +1 -1
- package/core/server/services/post-scheduling/post-scheduling.ts +1 -1
- package/core/server/services/posts/post-email-handler.js +13 -6
- package/core/server/services/posts/post-scheduling.js +1 -1
- package/core/server/services/posts/posts-service-instance.js +1 -1
- package/core/server/services/posts/posts-service.js +2 -2
- package/core/server/services/posts-public/service.js +1 -1
- package/core/server/services/public-config/site.js +1 -1
- package/core/server/services/recommendations/recommendation-service-wrapper.js +1 -1
- package/core/server/services/recommendations/service/libraries.d.ts +0 -2
- package/core/server/services/route-settings/dynamic-routing-service.js +2 -3
- package/core/server/services/route-settings/index.js +1 -1
- package/core/server/services/route-settings/route-settings-parser.js +134 -124
- package/core/server/services/route-settings/route-settings-parser.ts +160 -125
- package/core/server/services/route-settings/validation-errors.js +178 -0
- package/core/server/services/route-settings/validation-errors.ts +196 -0
- package/core/server/services/settings/settings-service.js +2 -2
- package/core/server/services/settings-helpers/index.js +1 -1
- package/core/server/services/slack-notifications/service.js +1 -1
- package/core/server/services/slack-ping/constants.js +16 -0
- package/core/server/services/slack-ping/constants.ts +15 -0
- package/core/server/services/slack-ping/index.js +26 -204
- package/core/server/services/slack-ping/index.ts +35 -0
- package/core/server/services/slack-ping/slack-ping-service.js +214 -0
- package/core/server/services/slack-ping/slack-ping-service.ts +296 -0
- package/core/server/services/staff/index.js +1 -1
- package/core/server/services/stats/posts-stats-service.js +1 -1
- package/core/server/services/stats/service.js +1 -1
- package/core/server/services/stripe/service.js +1 -1
- package/core/server/services/tags-public/service.js +1 -1
- package/core/server/services/themes/validate.js +1 -1
- package/core/server/services/update-check/index.js +1 -1
- package/core/server/services/url/lazy-url-service.js +66 -6
- package/core/server/services/url/lazy-url-service.ts +73 -6
- package/core/server/services/url/url-generator.js +1 -1
- package/core/server/services/url/url-service-facade.js +68 -3
- package/core/server/services/url/url-service-facade.ts +79 -4
- package/core/server/services/url/url-service.js +1 -1
- package/core/server/services/url/urls.js +1 -1
- package/core/server/services/webhooks/index.js +19 -2
- package/core/server/services/webhooks/listen.js +6 -11
- package/core/server/services/webhooks/payload.js +1 -3
- package/core/server/services/webhooks/serialize.js +16 -1
- package/core/server/web/admin/app.js +1 -1
- package/core/server/web/admin/middleware/redirect-admin-urls.js +1 -1
- package/core/server/web/api/endpoints/admin/routes.js +1 -0
- package/core/server/web/api/middleware/cors.js +1 -1
- package/core/server/web/api/middleware/upload.js +4 -0
- package/core/server/web/gift-preview/controller.js +1 -1
- package/core/server/web/shared/middleware/redirect-amp-urls.js +1 -1
- package/core/server/web/shared/middleware/uncapitalise.js +2 -2
- package/core/server/web/shared/middleware/url-redirects.js +1 -1
- package/core/shared/config/defaults.json +8 -1
- package/core/shared/config/overrides.json +4 -0
- package/core/shared/url-utils.js +19 -15
- package/core/shared/url-utils.ts +20 -0
- package/ghost.js +0 -1
- package/package.json +26 -31
- package/pnpm-lock.yaml +681 -2789
- package/pnpm-workspace.yaml +31 -17
- package/components/tryghost-adapter-base-scheduling-0.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.0.0.tgz +0 -0
- package/core/built/admin/assets/Combination--5EZGaDs.js +0 -43
- package/core/built/admin/assets/Growth-C0NoKcHQ.js +0 -3
- package/core/built/admin/assets/Newsletters-Ed8kDjdy.js +0 -1
- package/core/built/admin/assets/Overview-CArPgRZt.js +0 -1
- package/core/built/admin/assets/Web-CjjYoVM3.js +0 -2
- package/core/built/admin/assets/account-migration-mfa5mNI9.js +0 -1
- package/core/built/admin/assets/activitypub/inbox-B3rjKMTv.js +0 -1221
- package/core/built/admin/assets/activitypub/layout-jcurg3rU.js +0 -1
- package/core/built/admin/assets/activitypub/use-activity-pub-queries-yqGRcnLn.js +0 -2569
- package/core/built/admin/assets/activitypub/users-CfyHIsTG.js +0 -1316
- package/core/built/admin/assets/automations-DkUnxNdv.js +0 -1
- package/core/built/admin/assets/automations-vjXofmV_.js +0 -1
- package/core/built/admin/assets/bluesky-sharing-T-pO3RcP.js +0 -1
- package/core/built/admin/assets/breadcrumb-CekPYTCn.js +0 -1
- package/core/built/admin/assets/button--i2uMDDn.js +0 -4
- package/core/built/admin/assets/card-CSWz_BXq.js +0 -1
- package/core/built/admin/assets/chart-helpers-C_4Ka_vT.js +0 -1
- package/core/built/admin/assets/chunk.524.8ce2c741473e2f0929dc.js +0 -25
- package/core/built/admin/assets/chunk.582.453459d6228d2955408b.js +0 -27
- package/core/built/admin/assets/code-editor-view-BfkwQto2.js +0 -1
- package/core/built/admin/assets/command-K75Jzs0G.js +0 -1
- package/core/built/admin/assets/comments-BEIi1DH-.js +0 -1
- package/core/built/admin/assets/container-BNXvlcjA.js +0 -1
- package/core/built/admin/assets/data-list-BmK5QMab.js +0 -1
- package/core/built/admin/assets/date-range-select-5sKzwbt7.js +0 -1
- package/core/built/admin/assets/disabled-sources-indicator-L_ssJTNr.js +0 -1
- package/core/built/admin/assets/dist-BBIJuyPg.js +0 -1
- package/core/built/admin/assets/dist-BHE0dp6o.js +0 -1
- package/core/built/admin/assets/dist-DJ6c4_De.js +0 -1
- package/core/built/admin/assets/dist-DW0QXQO3.js +0 -1
- package/core/built/admin/assets/dist-DxFLceO5.js +0 -1
- package/core/built/admin/assets/dist-QcACAOAD.js +0 -1
- package/core/built/admin/assets/dist-duxRDcQu.js +0 -177
- package/core/built/admin/assets/domain-CLaPZ9Rc.js +0 -1
- package/core/built/admin/assets/dropzone-E0bSX4_a.js +0 -5
- package/core/built/admin/assets/edit-profile-DqNPKaA4.js +0 -1
- package/core/built/admin/assets/editor-CkLXQDRp.js +0 -8
- package/core/built/admin/assets/empty-indicator-BTtOlgIo.js +0 -1
- package/core/built/admin/assets/esm-TSfGW7nz.js +0 -2
- package/core/built/admin/assets/explore-BEstLxKr.js +0 -1
- package/core/built/admin/assets/feed-C58p2voG.js +0 -1
- package/core/built/admin/assets/field-BSbDUHPv.js +0 -1
- package/core/built/admin/assets/filters-BgYv8Gt7.js +0 -1
- package/core/built/admin/assets/filters-DvenFkmK.js +0 -1
- package/core/built/admin/assets/framework-provider-jQktxm9P.js +0 -4
- package/core/built/admin/assets/ghost-dark-8590dea6cd695ff90f27d262c4464538.css +0 -1
- package/core/built/admin/assets/gift-link-modal-BPjXkEFt.js +0 -1
- package/core/built/admin/assets/growth-D4JNiVTe.js +0 -1
- package/core/built/admin/assets/hooks-Cjxexi_I.js +0 -2
- package/core/built/admin/assets/index-BacwlcOy.js +0 -67
- package/core/built/admin/assets/index-DxRNHH0o.css +0 -1
- package/core/built/admin/assets/inline-C22rnPro.js +0 -1
- package/core/built/admin/assets/input-group-CKfrILLP.js +0 -1
- package/core/built/admin/assets/jsx-runtime-CtMZ-PAS.js +0 -1
- package/core/built/admin/assets/kpi-card-DZxQKAIb.js +0 -1
- package/core/built/admin/assets/kpi-card-FidQ5zg3.js +0 -1
- package/core/built/admin/assets/kpi-helpers-C5T8Vaf1.js +0 -1
- package/core/built/admin/assets/kpi-tabs-_VRK6hGV.js +0 -1
- package/core/built/admin/assets/label-Cza7hPgn.js +0 -1
- package/core/built/admin/assets/label-picker-DgC_zLaw.js +0 -1
- package/core/built/admin/assets/layout-BxsXnKFX.js +0 -1
- package/core/built/admin/assets/limit-service-CTurluJQ.js +0 -20
- package/core/built/admin/assets/link-helpers-C3Gqy2cz.js +0 -1
- package/core/built/admin/assets/list-page-BtqCnow_.js +0 -1
- package/core/built/admin/assets/member-custom-fields-9uXFBqSt.js +0 -1
- package/core/built/admin/assets/member-detail-Dq6ZDIxo.js +0 -1
- package/core/built/admin/assets/members-CUWD9jal.js +0 -10
- package/core/built/admin/assets/modals-CIdwHVga.js +0 -31
- package/core/built/admin/assets/moderation-DTt04UDA.js +0 -1
- package/core/built/admin/assets/newsletter-cC8vcc9h.js +0 -1
- package/core/built/admin/assets/note-DZU0IY9e.js +0 -1
- package/core/built/admin/assets/notifications-NoHpBHJ4.js +0 -1
- package/core/built/admin/assets/nql-lang-CvCSOQG3.js +0 -19
- package/core/built/admin/assets/onboarding-route-Deg2hNm8.js +0 -1
- package/core/built/admin/assets/overview-BQPt_9w3.js +0 -1
- package/core/built/admin/assets/page-header-D6bEqPo2.js +0 -1
- package/core/built/admin/assets/post-analytics-header-BPw_B2_D.js +0 -1
- package/core/built/admin/assets/post-analytics-kp2_--XO.js +0 -1
- package/core/built/admin/assets/post-share-modal-Cave3Z4W.js +0 -1
- package/core/built/admin/assets/preferences-C7h3j_uy.js +0 -5
- package/core/built/admin/assets/profile-B2x4q2oB.js +0 -1
- package/core/built/admin/assets/router-provider-5fsX1CYn.js +0 -1
- package/core/built/admin/assets/select-PhPjIBBT.js +0 -1
- package/core/built/admin/assets/settings-D5P9Gwgo.js +0 -6
- package/core/built/admin/assets/settings-zUOzQV5I.js +0 -1
- package/core/built/admin/assets/share-modal-EfpfbuWF.js +0 -1
- package/core/built/admin/assets/social-urls-DgAF08Xc.js +0 -18
- package/core/built/admin/assets/sort-button-WOQYJm2H.js +0 -1
- package/core/built/admin/assets/source-icon-hm8Cj-Gx.js +0 -1
- package/core/built/admin/assets/stats-CnQDb62m.js +0 -1
- package/core/built/admin/assets/stats-view-CCfTJgSe.js +0 -1
- package/core/built/admin/assets/step-1-BGlhY4DS.js +0 -1
- package/core/built/admin/assets/step-2-D3sDEyFH.js +0 -1
- package/core/built/admin/assets/step-3-CvZoUdsc.js +0 -2
- package/core/built/admin/assets/switch-CNjkOmwz.js +0 -1
- package/core/built/admin/assets/table-C5nZYPnW.js +0 -1
- package/core/built/admin/assets/tabs-dtOcExye.js +0 -1
- package/core/built/admin/assets/tags-Dhm0FB_g.js +0 -1
- package/core/built/admin/assets/textarea-CeKw3wmV.js +0 -1
- package/core/built/admin/assets/toggle-group-DfhZIMTW.js +0 -1
- package/core/built/admin/assets/topic-filter-Cxe1Il41.js +0 -1
- package/core/built/admin/assets/typeof-Ca-duI-z.js +0 -1
- package/core/built/admin/assets/use-filter-params-Bub6BqNy.js +0 -1
- package/core/built/admin/assets/use-growth-stats-BJORomv0.js +0 -1
- package/core/built/admin/assets/use-reply-chain-data-B89YjVoo.js +0 -1
- package/core/built/admin/assets/use-simple-pagination-BL2SYXKi.js +0 -1
- package/core/built/admin/assets/virtual-list-DNJOSK9I.js +0 -1
- package/core/built/admin/assets/web-BHUE3YQc.js +0 -1
- package/core/server/adapters/scheduling/scheduling-base.js +0 -5
- package/core/server/adapters/sso/SSOBase.js +0 -5
- package/core/server/adapters/storage/index.js +0 -17
|
@@ -11,42 +11,62 @@ import type {
|
|
|
11
11
|
TemplateRoute
|
|
12
12
|
} from '@tryghost/adapter-base-route-settings';
|
|
13
13
|
import {z} from 'zod';
|
|
14
|
-
import errors from '@tryghost/errors';
|
|
15
14
|
import tpl from '@tryghost/tpl';
|
|
15
|
+
import type {PathSegment} from './validation-errors';
|
|
16
|
+
import {describeValue, formatLocation, humanList, toValidationError, validationError} from './validation-errors';
|
|
16
17
|
|
|
17
18
|
const messages = {
|
|
18
|
-
|
|
19
|
-
badDataError: 'Please wrap the data definition into a custom name.',
|
|
19
|
+
badDataError: '"{key}" is a reserved key. Please wrap the data definition into a custom name.',
|
|
20
20
|
badDataHelp: 'Example:\n data:\n my-tag:\n resource: tags\n ...\n',
|
|
21
|
-
authorDeprecatedError: 'Please choose a different name. We recommend not using author.'
|
|
21
|
+
authorDeprecatedError: '"author" is reserved. Please choose a different name. We recommend not using author.'
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
function validationError(at: string, reason: string, help?: string): errors.ValidationError {
|
|
25
|
-
return new errors.ValidationError({
|
|
26
|
-
message: tpl(messages.validationError, {at, reason}),
|
|
27
|
-
...(help ? {help} : {})
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
24
|
const VALID_SHORTFORM_RESOURCES = ['tag', 'page', 'post', 'author'] as const;
|
|
32
25
|
const VALID_LONGFORM_RESOURCES = ['tags', 'posts', 'pages', 'authors'] as const;
|
|
33
26
|
const RESERVED_DATA_KEYS = ['resource', 'type', 'limit', 'order', 'include', 'filter', 'status', 'visibility', 'slug', 'redirect'];
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
// YAML parses a bare `filter:` as null and authors commonly quote numeric scalars
|
|
29
|
+
// (`limit: "100"`); the legacy validator accepted both, so treat an explicitly empty
|
|
30
|
+
// value as unset and coerce digit-only limit strings to numbers.
|
|
31
|
+
const OptionalStringField = z.string().nullish().transform(v => v ?? undefined);
|
|
32
|
+
const OptionalBooleanField = z.boolean().nullish().transform(v => v ?? undefined);
|
|
33
|
+
const LimitField = z.union([z.number(), z.literal('all'), z.string().regex(/^\d+$/).transform(Number)])
|
|
34
|
+
.nullish().transform(v => v ?? undefined);
|
|
35
|
+
|
|
36
|
+
const SHORTFORM_HELP = 'e.g. data: tag.recipes';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Shorthand data definitions look like `tag.recipes` — resource, dot, slug.
|
|
40
|
+
*
|
|
41
|
+
* `%s` stands in for the slug: on channel routes and collections, fetch-data
|
|
42
|
+
* substitutes it with the request's `slug` param at query time, which is how a
|
|
43
|
+
* wildcard route such as `/author/:slug/` serves a different author per request.
|
|
44
|
+
* There is no static slug that could replace it, so rejecting it would break
|
|
45
|
+
* those routes.
|
|
46
|
+
*/
|
|
47
|
+
function validateDataShortForm(value: string, path: PathSegment[]): void {
|
|
48
|
+
if (!/^[a-zA-Z0-9_]+\.(?:%s|[a-zA-Z0-9_-]+)$/.test(value)) {
|
|
49
|
+
throw validationError(
|
|
50
|
+
formatLocation(path),
|
|
51
|
+
`"${value}" is not a valid data shorthand. Please use resource.slug, e.g. tag.recipes.`,
|
|
52
|
+
SHORTFORM_HELP
|
|
53
|
+
);
|
|
39
54
|
}
|
|
40
|
-
|
|
55
|
+
|
|
56
|
+
const resource = value.split('.')[0];
|
|
41
57
|
if (!(VALID_SHORTFORM_RESOURCES as readonly string[]).includes(resource)) {
|
|
42
|
-
|
|
58
|
+
throw validationError(
|
|
59
|
+
formatLocation(path),
|
|
60
|
+
`resource "${resource}" is not supported. Please use ${humanList(VALID_SHORTFORM_RESOURCES)}.`,
|
|
61
|
+
SHORTFORM_HELP
|
|
62
|
+
);
|
|
43
63
|
}
|
|
44
|
-
}
|
|
64
|
+
}
|
|
45
65
|
|
|
46
66
|
const DataReadEntrySchema = z.object({
|
|
47
67
|
type: z.literal('read'),
|
|
48
68
|
resource: z.enum(VALID_LONGFORM_RESOURCES),
|
|
49
|
-
slug: z.string(
|
|
69
|
+
slug: z.string().min(1),
|
|
50
70
|
redirect: z.boolean().optional(),
|
|
51
71
|
include: z.string().optional(),
|
|
52
72
|
visibility: z.string().optional(),
|
|
@@ -56,90 +76,104 @@ const DataReadEntrySchema = z.object({
|
|
|
56
76
|
const DataBrowseEntrySchema = z.object({
|
|
57
77
|
type: z.literal('browse'),
|
|
58
78
|
resource: z.enum(VALID_LONGFORM_RESOURCES),
|
|
59
|
-
filter:
|
|
60
|
-
limit:
|
|
61
|
-
order:
|
|
62
|
-
include:
|
|
63
|
-
fields:
|
|
64
|
-
visibility:
|
|
65
|
-
status:
|
|
79
|
+
filter: OptionalStringField,
|
|
80
|
+
limit: LimitField,
|
|
81
|
+
order: OptionalStringField,
|
|
82
|
+
include: OptionalStringField,
|
|
83
|
+
fields: OptionalStringField,
|
|
84
|
+
visibility: OptionalStringField,
|
|
85
|
+
status: OptionalStringField,
|
|
66
86
|
page: z.number().optional()
|
|
67
87
|
});
|
|
68
88
|
|
|
69
89
|
const DataLongFormEntrySchema = z.discriminatedUnion('type', [DataReadEntrySchema, DataBrowseEntrySchema]);
|
|
70
90
|
|
|
71
|
-
|
|
91
|
+
const DATA_ENTRY_HELP = 'e.g.\n data:\n my-tag:\n resource: tags\n type: read\n slug: recipes\n';
|
|
92
|
+
|
|
93
|
+
function parseDataEntry(value: unknown, path: PathSegment[]): DataEntry {
|
|
72
94
|
if (typeof value === 'string') {
|
|
73
|
-
|
|
74
|
-
if (!result.success) {
|
|
75
|
-
throw toValidationError(result.error);
|
|
76
|
-
}
|
|
95
|
+
validateDataShortForm(value, path);
|
|
77
96
|
return value as DataShortForm;
|
|
78
97
|
}
|
|
79
98
|
|
|
80
|
-
if (typeof value === 'object' && value !== null) {
|
|
99
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
81
100
|
const result = DataLongFormEntrySchema.safeParse(value);
|
|
82
101
|
if (!result.success) {
|
|
83
102
|
const issue = result.error.issues[0];
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
103
|
+
const at = formatLocation(path);
|
|
104
|
+
const record = value as Record<string, unknown>;
|
|
105
|
+
|
|
106
|
+
// A failed `type` discriminator surfaces as a union issue on that
|
|
107
|
+
// key — matched on the code rather than the wording, which zod
|
|
108
|
+
// is free to change.
|
|
109
|
+
if (issue.code === 'invalid_union' && issue.path[0] === 'type') {
|
|
110
|
+
if (record.type === undefined) {
|
|
111
|
+
throw validationError(at, 'type is required. Please use read or browse.', DATA_ENTRY_HELP);
|
|
88
112
|
}
|
|
89
|
-
throw validationError(
|
|
113
|
+
throw validationError(at, `type "${record.type}" is not supported. Please use read or browse.`, DATA_ENTRY_HELP);
|
|
90
114
|
}
|
|
91
|
-
if (issue.path
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
throw validationError(JSON.stringify(value), 'resource is required.');
|
|
115
|
+
if (issue.path.includes('resource')) {
|
|
116
|
+
if (record.resource === undefined) {
|
|
117
|
+
throw validationError(at, `resource is required. Please use ${humanList(VALID_LONGFORM_RESOURCES)}.`, DATA_ENTRY_HELP);
|
|
95
118
|
}
|
|
96
|
-
throw validationError(
|
|
119
|
+
throw validationError(at, `resource "${record.resource}" is not supported. Please use ${humanList(VALID_LONGFORM_RESOURCES)}.`, DATA_ENTRY_HELP);
|
|
120
|
+
}
|
|
121
|
+
if (issue.path.includes('slug') && !record.slug) {
|
|
122
|
+
throw validationError(at, 'slug is required for read data entries.', DATA_ENTRY_HELP);
|
|
123
|
+
}
|
|
124
|
+
throw toValidationError(result.error, path, value);
|
|
125
|
+
}
|
|
126
|
+
// Fields set to an explicitly empty value parse to undefined — drop the
|
|
127
|
+
// keys so "unset" means absent in the domain model and serialized output.
|
|
128
|
+
const entry: Record<string, unknown> = result.data;
|
|
129
|
+
for (const entryKey of Object.keys(entry)) {
|
|
130
|
+
if (entry[entryKey] === undefined) {
|
|
131
|
+
delete entry[entryKey];
|
|
97
132
|
}
|
|
98
|
-
throw toValidationError(result.error);
|
|
99
133
|
}
|
|
100
134
|
return result.data;
|
|
101
135
|
}
|
|
102
136
|
|
|
103
|
-
throw validationError(
|
|
137
|
+
throw validationError(
|
|
138
|
+
formatLocation(path),
|
|
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
|
|
141
|
+
);
|
|
104
142
|
}
|
|
105
143
|
|
|
106
|
-
function parseRouteData(data: unknown): RouteData {
|
|
144
|
+
function parseRouteData(data: unknown, path: PathSegment[]): RouteData {
|
|
107
145
|
if (typeof data === 'string') {
|
|
108
|
-
|
|
109
|
-
if (!result.success) {
|
|
110
|
-
throw toValidationError(result.error);
|
|
111
|
-
}
|
|
146
|
+
validateDataShortForm(data, path);
|
|
112
147
|
return data as DataShortForm;
|
|
113
148
|
}
|
|
114
149
|
|
|
115
|
-
if (typeof data !== 'object' || data === null) {
|
|
116
|
-
throw validationError(
|
|
150
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
|
151
|
+
throw validationError(
|
|
152
|
+
formatLocation(path),
|
|
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
|
|
155
|
+
);
|
|
117
156
|
}
|
|
118
157
|
|
|
119
158
|
const record = data as Record<string, unknown>;
|
|
120
159
|
|
|
121
160
|
for (const key of Object.keys(record)) {
|
|
122
161
|
if (RESERVED_DATA_KEYS.includes(key)) {
|
|
123
|
-
throw
|
|
124
|
-
message: tpl(messages.badDataError),
|
|
125
|
-
help: messages.badDataHelp
|
|
126
|
-
});
|
|
162
|
+
throw validationError(formatLocation([...path, key]), tpl(messages.badDataError, {key}), messages.badDataHelp);
|
|
127
163
|
}
|
|
128
164
|
if (key === 'author') {
|
|
129
|
-
throw
|
|
130
|
-
message: tpl(messages.authorDeprecatedError)
|
|
131
|
-
});
|
|
165
|
+
throw validationError(formatLocation([...path, key]), messages.authorDeprecatedError);
|
|
132
166
|
}
|
|
133
167
|
}
|
|
134
168
|
|
|
135
169
|
const parsed: Record<string, DataEntry> = {};
|
|
136
170
|
for (const [key, value] of Object.entries(record)) {
|
|
137
|
-
parsed[key] = parseDataEntry(
|
|
171
|
+
parsed[key] = parseDataEntry(value, [...path, key]);
|
|
138
172
|
}
|
|
139
173
|
return parsed;
|
|
140
174
|
}
|
|
141
175
|
|
|
142
|
-
const TemplateField = z.union([z.string(), z.array(z.string())]).
|
|
176
|
+
const TemplateField = z.union([z.string(), z.array(z.string())]).nullish().default([])
|
|
143
177
|
.transform(v => {
|
|
144
178
|
if (!v || (Array.isArray(v) && v.length === 0)) {
|
|
145
179
|
return [];
|
|
@@ -147,18 +181,20 @@ const TemplateField = z.union([z.string(), z.array(z.string())]).optional().defa
|
|
|
147
181
|
return Array.isArray(v) ? v : [v];
|
|
148
182
|
});
|
|
149
183
|
|
|
150
|
-
|
|
184
|
+
// The schemas are built per route/collection so that errors raised while
|
|
185
|
+
// parsing nested `data` can name the exact path they came from.
|
|
186
|
+
const routeObjectSchema = (path: PathSegment[]) => z.object({
|
|
151
187
|
controller: z.literal('channel').optional(),
|
|
152
188
|
template: TemplateField,
|
|
153
189
|
data: z.unknown().optional(),
|
|
154
|
-
content_type:
|
|
155
|
-
filter:
|
|
156
|
-
order:
|
|
157
|
-
limit:
|
|
158
|
-
rss:
|
|
190
|
+
content_type: OptionalStringField,
|
|
191
|
+
filter: OptionalStringField,
|
|
192
|
+
order: OptionalStringField,
|
|
193
|
+
limit: LimitField,
|
|
194
|
+
rss: OptionalBooleanField
|
|
159
195
|
}).transform((val): Omit<Route, 'path'> => {
|
|
160
196
|
const templates = val.template;
|
|
161
|
-
const data = val.data !== undefined ? parseRouteData(val.data) : undefined;
|
|
197
|
+
const data = val.data !== undefined ? parseRouteData(val.data, [...path, 'data']) : undefined;
|
|
162
198
|
|
|
163
199
|
if (val.controller === 'channel') {
|
|
164
200
|
const route: Omit<ChannelRoute, 'path'> = {
|
|
@@ -199,16 +235,16 @@ const RouteObjectSchema = z.object({
|
|
|
199
235
|
return route;
|
|
200
236
|
});
|
|
201
237
|
|
|
202
|
-
const
|
|
203
|
-
permalink: z.string(
|
|
238
|
+
const collectionValueSchema = (path: PathSegment[]) => z.object({
|
|
239
|
+
permalink: z.string().optional(),
|
|
204
240
|
template: TemplateField,
|
|
205
241
|
data: z.unknown().optional(),
|
|
206
|
-
filter:
|
|
207
|
-
order:
|
|
208
|
-
limit:
|
|
209
|
-
rss:
|
|
242
|
+
filter: OptionalStringField,
|
|
243
|
+
order: OptionalStringField,
|
|
244
|
+
limit: LimitField,
|
|
245
|
+
rss: OptionalBooleanField
|
|
210
246
|
}).transform((val): Omit<CollectionConfig, 'path'> => {
|
|
211
|
-
const data = val.data !== undefined ? parseRouteData(val.data) : undefined;
|
|
247
|
+
const data = val.data !== undefined ? parseRouteData(val.data, [...path, 'data']) : undefined;
|
|
212
248
|
const collection: Omit<CollectionConfig, 'path'> = {
|
|
213
249
|
permalink: val.permalink ?? '',
|
|
214
250
|
templates: val.template
|
|
@@ -233,15 +269,35 @@ const RawCollectionValueSchema = z.object({
|
|
|
233
269
|
|
|
234
270
|
const RouteSettingsSchema = z.object({
|
|
235
271
|
routes: z.record(z.string(), z.unknown()).nullable().optional().default({}),
|
|
236
|
-
collections: z.record(z.string(),
|
|
272
|
+
collections: z.record(z.string(), z.unknown()).nullable().optional().default({}),
|
|
237
273
|
taxonomies: z.record(z.string(), z.string()).nullable().optional().default({})
|
|
238
274
|
});
|
|
239
275
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Every path in routes.yaml — route paths, collection paths, permalinks and
|
|
278
|
+
* taxonomy permalinks — follows the same rules, so they share one message set.
|
|
279
|
+
*
|
|
280
|
+
* `allowParamNotation` is set for route and collection keys, which are mounted on
|
|
281
|
+
* Express verbatim: nothing rewrites `{param}` to `:param` for them the way it
|
|
282
|
+
* does for permalinks and taxonomies. So `/author/:slug/` is a working wildcard
|
|
283
|
+
* route on live sites and `/author/{slug}/` would be a dead literal path —
|
|
284
|
+
* rejecting :param there would break the former, and the suggested rewrite would
|
|
285
|
+
* silently produce the latter.
|
|
286
|
+
*/
|
|
287
|
+
function validatePath(value: string, path: PathSegment[], example: string, {allowParamNotation = false}: {allowParamNotation?: boolean} = {}): void {
|
|
288
|
+
const at = formatLocation(path);
|
|
289
|
+
|
|
290
|
+
if (!value.startsWith('/')) {
|
|
291
|
+
throw validationError(at, `"${value}" is missing a leading slash. Please use e.g. ${example}.`);
|
|
292
|
+
}
|
|
293
|
+
if (!value.endsWith('/')) {
|
|
294
|
+
throw validationError(at, `"${value}" is missing a trailing slash. Please use e.g. ${example}.`);
|
|
295
|
+
}
|
|
296
|
+
if (!allowParamNotation && /\/:\w+/.test(value)) {
|
|
297
|
+
// Suggest the same path in the notation Ghost expects, rather than a
|
|
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}')}".`);
|
|
300
|
+
}
|
|
245
301
|
}
|
|
246
302
|
|
|
247
303
|
export function parseRouteSettings(raw: unknown, yamlSource: string): RouteSettings {
|
|
@@ -249,7 +305,7 @@ export function parseRouteSettings(raw: unknown, yamlSource: string): RouteSetti
|
|
|
249
305
|
|
|
250
306
|
const parsed = RouteSettingsSchema.safeParse(obj);
|
|
251
307
|
if (!parsed.success) {
|
|
252
|
-
throw toValidationError(parsed.error);
|
|
308
|
+
throw toValidationError(parsed.error, [], obj);
|
|
253
309
|
}
|
|
254
310
|
|
|
255
311
|
const {routes: rawRoutes, collections: rawCollections, taxonomies: rawTaxonomies} = parsed.data;
|
|
@@ -257,18 +313,11 @@ export function parseRouteSettings(raw: unknown, yamlSource: string): RouteSetti
|
|
|
257
313
|
const routes: Route[] = [];
|
|
258
314
|
if (rawRoutes) {
|
|
259
315
|
for (const [path, value] of Object.entries(rawRoutes)) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
if (!path.endsWith('/')) {
|
|
264
|
-
throw validationError(path, 'A trailing slash is required.');
|
|
265
|
-
}
|
|
266
|
-
if (/\/:\w+/.test(path)) {
|
|
267
|
-
throw validationError(path, 'Please use the following notation e.g. /{slug}/.');
|
|
268
|
-
}
|
|
316
|
+
const routeLocation: PathSegment[] = ['routes', path];
|
|
317
|
+
validatePath(path, routeLocation, '/about/', {allowParamNotation: true});
|
|
269
318
|
|
|
270
319
|
if (value === null || value === undefined) {
|
|
271
|
-
throw validationError(
|
|
320
|
+
throw validationError(formatLocation(routeLocation), 'Please define a template, e.g. /about/: about.');
|
|
272
321
|
}
|
|
273
322
|
|
|
274
323
|
if (typeof value === 'string') {
|
|
@@ -276,14 +325,14 @@ export function parseRouteSettings(raw: unknown, yamlSource: string): RouteSetti
|
|
|
276
325
|
continue;
|
|
277
326
|
}
|
|
278
327
|
|
|
279
|
-
const routeResult =
|
|
328
|
+
const routeResult = routeObjectSchema(routeLocation).safeParse(value);
|
|
280
329
|
if (!routeResult.success) {
|
|
281
|
-
throw toValidationError(routeResult.error);
|
|
330
|
+
throw toValidationError(routeResult.error, routeLocation, value);
|
|
282
331
|
}
|
|
283
332
|
|
|
284
333
|
const route = routeResult.data;
|
|
285
334
|
if (route.type === 'template' && (!route.templates || route.templates.length === 0) && !route.data && !(route as Omit<TemplateRoute, 'path'>).contentType) {
|
|
286
|
-
throw validationError(
|
|
335
|
+
throw validationError(formatLocation(routeLocation), 'Please define a template, e.g. /about/: about.');
|
|
287
336
|
}
|
|
288
337
|
|
|
289
338
|
routes.push({...route, path} as Route);
|
|
@@ -293,49 +342,35 @@ export function parseRouteSettings(raw: unknown, yamlSource: string): RouteSetti
|
|
|
293
342
|
const collections: CollectionConfig[] = [];
|
|
294
343
|
if (rawCollections) {
|
|
295
344
|
for (const [path, value] of Object.entries(rawCollections)) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (/\/:\w+/.test(path)) {
|
|
303
|
-
throw validationError(path, 'Please use the following notation e.g. /{slug}/.');
|
|
304
|
-
}
|
|
305
|
-
if (!value.permalink) {
|
|
306
|
-
throw validationError(path, 'Please define a permalink route.', 'e.g. permalink: /{slug}/');
|
|
307
|
-
}
|
|
308
|
-
if (!value.permalink.startsWith('/')) {
|
|
309
|
-
throw validationError(value.permalink, 'A leading slash is required.');
|
|
310
|
-
}
|
|
311
|
-
if (!value.permalink.endsWith('/')) {
|
|
312
|
-
throw validationError(value.permalink, 'A trailing slash is required.');
|
|
345
|
+
const collectionLocation: PathSegment[] = ['collections', path];
|
|
346
|
+
validatePath(path, collectionLocation, '/blog/', {allowParamNotation: true});
|
|
347
|
+
|
|
348
|
+
const collectionResult = collectionValueSchema(collectionLocation).safeParse(value);
|
|
349
|
+
if (!collectionResult.success) {
|
|
350
|
+
throw toValidationError(collectionResult.error, collectionLocation, value);
|
|
313
351
|
}
|
|
314
|
-
|
|
315
|
-
|
|
352
|
+
|
|
353
|
+
const collection = collectionResult.data;
|
|
354
|
+
if (!collection.permalink) {
|
|
355
|
+
throw validationError(formatLocation(collectionLocation), 'Please define a permalink route, e.g. permalink: /{slug}/.');
|
|
316
356
|
}
|
|
317
|
-
|
|
357
|
+
validatePath(collection.permalink, [...collectionLocation, 'permalink'], '/{slug}/');
|
|
358
|
+
|
|
359
|
+
collections.push({...collection, path});
|
|
318
360
|
}
|
|
319
361
|
}
|
|
320
362
|
|
|
321
363
|
const taxonomies: TaxonomyConfig = {};
|
|
322
364
|
if (rawTaxonomies) {
|
|
323
365
|
for (const [key, value] of Object.entries(rawTaxonomies)) {
|
|
366
|
+
const taxonomyLocation: PathSegment[] = ['taxonomies', key];
|
|
324
367
|
if (!['tag', 'author'].includes(key)) {
|
|
325
|
-
throw validationError(
|
|
368
|
+
throw validationError(formatLocation(taxonomyLocation), 'Unknown taxonomy. Please use tag or author.');
|
|
326
369
|
}
|
|
327
370
|
if (!value) {
|
|
328
|
-
throw validationError(
|
|
329
|
-
}
|
|
330
|
-
if (!value.startsWith('/')) {
|
|
331
|
-
throw validationError(value, 'A leading slash is required.');
|
|
332
|
-
}
|
|
333
|
-
if (!value.endsWith('/')) {
|
|
334
|
-
throw validationError(value, 'A trailing slash is required.');
|
|
335
|
-
}
|
|
336
|
-
if (/\/:\w+/.test(value)) {
|
|
337
|
-
throw validationError(value, 'Please use the following notation e.g. /{slug}/.');
|
|
371
|
+
throw validationError(formatLocation(taxonomyLocation), `Please define a taxonomy permalink route, e.g. ${key}: /${key}/{slug}/.`);
|
|
338
372
|
}
|
|
373
|
+
validatePath(value, taxonomyLocation, `/${key}/{slug}/`);
|
|
339
374
|
if (key === 'tag') {
|
|
340
375
|
taxonomies.tag = value;
|
|
341
376
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatLocation = formatLocation;
|
|
7
|
+
exports.describeValue = describeValue;
|
|
8
|
+
exports.humanList = humanList;
|
|
9
|
+
exports.validationError = validationError;
|
|
10
|
+
exports.toValidationError = toValidationError;
|
|
11
|
+
const errors_1 = __importDefault(require("@tryghost/errors"));
|
|
12
|
+
const tpl_1 = __importDefault(require("@tryghost/tpl"));
|
|
13
|
+
const messages = {
|
|
14
|
+
validationError: 'The following definition "{at}" is invalid: {reason}'
|
|
15
|
+
};
|
|
16
|
+
const DOCS_URL = 'https://ghost.org/docs/themes/routing/';
|
|
17
|
+
const MAX_SHOWN_LENGTH = 40;
|
|
18
|
+
/**
|
|
19
|
+
* Renders the path to the offending value the way an author would find it in
|
|
20
|
+
* their file, e.g. `routes['/about/'].template[1]`.
|
|
21
|
+
*/
|
|
22
|
+
function formatLocation(path) {
|
|
23
|
+
if (path.length === 0) {
|
|
24
|
+
return 'routes.yaml';
|
|
25
|
+
}
|
|
26
|
+
return path.reduce((out, segment) => {
|
|
27
|
+
if (typeof segment === 'number') {
|
|
28
|
+
return `${out}[${segment}]`;
|
|
29
|
+
}
|
|
30
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(segment)) {
|
|
31
|
+
return out ? `${out}.${segment}` : segment;
|
|
32
|
+
}
|
|
33
|
+
return `${out}['${segment.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}']`;
|
|
34
|
+
}, '');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Names what the author actually wrote, so the error can contrast it with what
|
|
38
|
+
* was expected. Reads as the "but <this> was provided" half of a message.
|
|
39
|
+
*/
|
|
40
|
+
function describeValue(value) {
|
|
41
|
+
if (value === null || value === undefined) {
|
|
42
|
+
return 'nothing';
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
return 'a list';
|
|
46
|
+
}
|
|
47
|
+
if (value instanceof Date) {
|
|
48
|
+
return 'a date';
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === 'object') {
|
|
51
|
+
return 'a map';
|
|
52
|
+
}
|
|
53
|
+
if (typeof value === 'string') {
|
|
54
|
+
const shown = value.length > MAX_SHOWN_LENGTH ? `${value.slice(0, MAX_SHOWN_LENGTH)}…` : value;
|
|
55
|
+
return `the text "${shown}"`;
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
58
|
+
return String(value);
|
|
59
|
+
}
|
|
60
|
+
return 'an unsupported value';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Lists options the way a sentence would, e.g. "posts, pages or tags".
|
|
64
|
+
*/
|
|
65
|
+
function humanList(items) {
|
|
66
|
+
if (items.length < 2) {
|
|
67
|
+
return items.join('');
|
|
68
|
+
}
|
|
69
|
+
return `${items.slice(0, -1).join(', ')} or ${items[items.length - 1]}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* What each key in routes.yaml accepts, phrased so the message reads
|
|
73
|
+
* "<key> must be <expectation>".
|
|
74
|
+
*/
|
|
75
|
+
const FIELD_EXPECTATIONS = {
|
|
76
|
+
routes: 'a map of route paths (e.g. /about/: about)',
|
|
77
|
+
collections: 'a map of collection paths (e.g. /blog/: {permalink: /{slug}/})',
|
|
78
|
+
taxonomies: 'a map of tag and/or author permalinks (e.g. tag: /tag/{slug}/)',
|
|
79
|
+
controller: 'channel, the only supported controller',
|
|
80
|
+
template: 'a template name, or a list of template names (e.g. template: index)',
|
|
81
|
+
permalink: 'a route with a leading and trailing slash (e.g. permalink: /{slug}/)',
|
|
82
|
+
content_type: 'a content type (e.g. content_type: text/xml)',
|
|
83
|
+
filter: 'a filter string (e.g. filter: featured:true)',
|
|
84
|
+
order: 'an order string (e.g. order: published_at desc)',
|
|
85
|
+
limit: 'a number or "all" (e.g. limit: 5)',
|
|
86
|
+
include: 'a comma-separated string (e.g. include: authors,tags)',
|
|
87
|
+
fields: 'a comma-separated string (e.g. fields: title,slug)',
|
|
88
|
+
visibility: 'a string (e.g. visibility: public)',
|
|
89
|
+
status: 'a string (e.g. status: published)',
|
|
90
|
+
slug: 'a slug (e.g. slug: welcome)',
|
|
91
|
+
page: 'a number (e.g. page: 2)',
|
|
92
|
+
rss: 'true or false',
|
|
93
|
+
redirect: 'true or false'
|
|
94
|
+
};
|
|
95
|
+
const CONTAINER_EXPECTATIONS = {
|
|
96
|
+
routes: { subject: 'a route', expectation: 'a template name, or a map of route options (e.g. template, controller, data)' },
|
|
97
|
+
collections: { subject: 'a collection', expectation: 'a map of collection options (e.g. permalink, template, filter)' }
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Keys that accept a list: saying "a list was provided" would contradict the
|
|
101
|
+
* expectation, so the entries are blamed instead.
|
|
102
|
+
*/
|
|
103
|
+
const LIST_ENTRY_PROBLEMS = {
|
|
104
|
+
template: 'one or more of the entries is not a template name'
|
|
105
|
+
};
|
|
106
|
+
function lookup(table, key) {
|
|
107
|
+
return typeof key === 'string' && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : undefined;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The key a failure belongs to — for a list entry that is the key holding the
|
|
111
|
+
* list, not the index.
|
|
112
|
+
*/
|
|
113
|
+
function fieldKey(path) {
|
|
114
|
+
const last = path[path.length - 1];
|
|
115
|
+
return typeof last === 'number' ? path[path.length - 2] : last;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resolves the "<subject> must be <expectation>" half of a message for the
|
|
119
|
+
* value at `path`. Returns null when the key is unknown, in which case the
|
|
120
|
+
* caller falls back to the underlying schema message.
|
|
121
|
+
*/
|
|
122
|
+
function describeExpectation(path) {
|
|
123
|
+
if (path.length === 0) {
|
|
124
|
+
return { subject: 'the file', expectation: 'a map of routes, collections or taxonomies' };
|
|
125
|
+
}
|
|
126
|
+
const [section] = path;
|
|
127
|
+
// A whole section, e.g. `routes: hello`
|
|
128
|
+
if (path.length === 1) {
|
|
129
|
+
const expectation = lookup(FIELD_EXPECTATIONS, section);
|
|
130
|
+
return expectation ? { subject: String(section), expectation } : null;
|
|
131
|
+
}
|
|
132
|
+
// A single route or collection, e.g. `routes: {/about/: [a, b]}`
|
|
133
|
+
if (path.length === 2 && typeof section === 'string' && Object.prototype.hasOwnProperty.call(CONTAINER_EXPECTATIONS, section)) {
|
|
134
|
+
return CONTAINER_EXPECTATIONS[section];
|
|
135
|
+
}
|
|
136
|
+
// A taxonomy permalink, e.g. `taxonomies: {tag: 5}`
|
|
137
|
+
if (path.length === 2 && section === 'taxonomies') {
|
|
138
|
+
return { subject: `the ${String(path[1])} taxonomy`, expectation: `a permalink route (e.g. ${String(path[1])}: /${String(path[1])}/{slug}/)` };
|
|
139
|
+
}
|
|
140
|
+
const expectation = lookup(FIELD_EXPECTATIONS, fieldKey(path));
|
|
141
|
+
return expectation ? { subject: String(fieldKey(path)), expectation } : null;
|
|
142
|
+
}
|
|
143
|
+
function valueAtPath(value, path) {
|
|
144
|
+
return path.reduce((current, segment) => {
|
|
145
|
+
if (current === null || typeof current !== 'object') {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
return current[segment];
|
|
149
|
+
}, value);
|
|
150
|
+
}
|
|
151
|
+
function validationError(at, reason, help) {
|
|
152
|
+
return new errors_1.default.ValidationError({
|
|
153
|
+
message: (0, tpl_1.default)(messages.validationError, { at, reason }),
|
|
154
|
+
...(help ? { help } : {})
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Turns a schema failure into an error that names the exact key in routes.yaml
|
|
159
|
+
* and says what that key accepts — schema messages on their own only say
|
|
160
|
+
* things like "Invalid input: expected string, received number".
|
|
161
|
+
*
|
|
162
|
+
* @param error the schema failure
|
|
163
|
+
* @param basePath path of the value that was handed to the schema
|
|
164
|
+
* @param value that same value, used to describe what the author wrote
|
|
165
|
+
*/
|
|
166
|
+
function toValidationError(error, basePath = [], value) {
|
|
167
|
+
const issue = error.issues[0];
|
|
168
|
+
const issuePath = issue.path;
|
|
169
|
+
const path = [...basePath, ...issuePath];
|
|
170
|
+
const expectation = describeExpectation(path);
|
|
171
|
+
if (!expectation) {
|
|
172
|
+
return validationError(formatLocation(path), issue.message, DOCS_URL);
|
|
173
|
+
}
|
|
174
|
+
const provided = valueAtPath(value, issuePath);
|
|
175
|
+
const listProblem = Array.isArray(provided) ? lookup(LIST_ENTRY_PROBLEMS, fieldKey(path)) : undefined;
|
|
176
|
+
const found = listProblem ?? `${describeValue(provided)} was provided`;
|
|
177
|
+
return validationError(formatLocation(path), `${expectation.subject} must be ${expectation.expectation}, but ${found}.`, DOCS_URL);
|
|
178
|
+
}
|