ghost 6.55.0 → 6.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (483) hide show
  1. package/bin/validate-adapters.js +74 -0
  2. package/bin/validate-adapters.ts +87 -0
  3. package/bin/warm-compile-cache.sh +60 -0
  4. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  5. package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
  6. package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
  7. package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
  8. package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
  9. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  10. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  11. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  12. package/core/app.js +1 -1
  13. package/core/boot.js +21 -25
  14. package/core/bridge.js +10 -12
  15. package/core/built/admin/assets/Combination-BDvCdg1t.js +41 -0
  16. package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
  17. package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
  18. package/core/built/admin/assets/about-C7O-AgRZ.js +1 -0
  19. package/core/built/admin/assets/{account-migration-sjHLx5dy.js → account-migration-Cv_ol1ko.js} +1 -1
  20. package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
  21. package/core/built/admin/assets/activitypub/{account-migration-DoYXtnZ8.js → account-migration-DHFbePl6.js} +7 -7
  22. package/core/built/admin/assets/activitypub/activitypub.js +6 -6
  23. package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-CT4tS2_S.js} +2 -2
  24. package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-ByYWKipo.js} +2 -2
  25. package/core/built/admin/assets/activitypub/{bluesky-sharing-Bxeh6MKW.js → bluesky-sharing-srVnSHDz.js} +16 -16
  26. package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-C2sos5Ht.js} +3 -3
  27. package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-B1UBTOoZ.js} +2 -2
  28. package/core/built/admin/assets/activitypub/{dist-D8sFfdpC.js → dist-MPsb2WZ1.js} +12 -12
  29. package/core/built/admin/assets/activitypub/{domain-CWueFW6q.js → domain-DzcM9Dzo.js} +9 -9
  30. package/core/built/admin/assets/activitypub/{edit-profile-BGxPU7N5.js → edit-profile-CsQ_4xAk.js} +945 -945
  31. package/core/built/admin/assets/activitypub/{explore-DBK-u0Ch.js → explore-CJe0DZBK.js} +8 -8
  32. package/core/built/admin/assets/activitypub/{feed-Bcir5RQZ.js → feed-CKV3qWON.js} +33 -33
  33. package/core/built/admin/assets/activitypub/{field-BV-CIT3d.js → field-DckHfDJf.js} +3 -3
  34. package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-Dlp8RM55.js} +2 -2
  35. package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-CsPy4R0M.js} +2 -2
  36. package/core/built/admin/assets/activitypub/{inbox-yC0nfvzl.js → inbox-DAsjzUUe.js} +85 -85
  37. package/core/built/admin/assets/activitypub/{label-DTRS9TDk.js → label-DWyo9E5T.js} +3 -3
  38. package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +1 -0
  39. package/core/built/admin/assets/activitypub/{moderation-Dwl9t0pb.js → moderation-B0Inc03M.js} +11 -11
  40. package/core/built/admin/assets/activitypub/{note-B-zKsZ7r.js → note-CSE54lER.js} +7 -7
  41. package/core/built/admin/assets/activitypub/{notifications-DjJZD54t.js → notifications-RSwbKqdh.js} +9 -9
  42. package/core/built/admin/assets/activitypub/{preferences-CoU8vs6d.js → preferences-CE231n2_.js} +20 -20
  43. package/core/built/admin/assets/activitypub/{profile-BAsaJekx.js → profile-aaQ1j1Eo.js} +237 -237
  44. package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cjb0Vs3m.js} +2 -2
  45. package/core/built/admin/assets/activitypub/{routes-CLH3OVTP.js → routes-Djb0mc9k.js} +1139 -1150
  46. package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-D8pROI5n.js} +3 -3
  47. package/core/built/admin/assets/activitypub/{settings-DA6pNJ-Q.js → settings-DMc32IQl.js} +13 -13
  48. package/core/built/admin/assets/activitypub/{site-CQD42Pw5.js → site-DVIcsohX.js} +81 -81
  49. package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-9Yey9usF.js} +5 -5
  50. package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DdYmKRXV.js} +6 -6
  51. package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-hdSkcjZU.js} +10 -10
  52. package/core/built/admin/assets/activitypub/{tabs-Cg0saow1.js → tabs-CUqseYzQ.js} +317 -315
  53. package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-Coni6OXM.js} +3 -3
  54. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
  55. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-B046AM38.js} +115 -96
  56. package/core/built/admin/assets/activitypub/{use-reply-chain-data-Br4aOWOh.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
  57. package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-BRfRYd16.js} +2 -2
  58. package/core/built/admin/assets/add-integration-modal-m9Dv6f77.js +1 -0
  59. package/core/built/admin/assets/add-newsletter-modal-CPSVZL2H.js +1 -0
  60. package/core/built/admin/assets/add-recommendation-modal-Ci9Z5yat.js +1 -0
  61. package/core/built/admin/assets/analytics-DqSCVV8U.js +1 -0
  62. package/core/built/admin/assets/announcement-bar-modal-DU3avV7d.js +1 -0
  63. package/core/built/admin/assets/api-keys-BK0LylyK.js +1 -0
  64. package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
  65. package/core/built/admin/assets/{audience-DjEWNhp3.js → audience-DTJtXXWY.js} +1 -1
  66. package/core/built/admin/assets/automated-emails-PayPCT8L.js +1 -0
  67. package/core/built/admin/assets/{automations-DhKeS_U5.js → automations-162q5HjL.js} +1 -1
  68. package/core/built/admin/assets/{automations-Bx3VcFIL.js → automations-dtQ_MRZ7.js} +1 -1
  69. package/core/built/admin/assets/{bluesky-sharing-ClMd0-Ft.js → bluesky-sharing-B8Es3AuV.js} +1 -1
  70. package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
  71. package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
  72. package/core/built/admin/assets/{breadcrumb-BD0U2ygg.js → breadcrumb-tg5zL--z.js} +1 -1
  73. package/core/built/admin/assets/build-CsiH26uS.js +1 -0
  74. package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
  75. package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
  76. package/core/built/admin/assets/{chart-helpers-fjQKRVyY.js → chart-helpers-CZElTGYL.js} +1 -1
  77. package/core/built/admin/assets/checkbox-Cyo-x7mC.js +1 -0
  78. package/core/built/admin/assets/chunk.524.cd5498dd4e195f679266.js +25 -0
  79. package/core/built/admin/assets/chunk.582.6f92ec1f03734bf2ea3f.js +27 -0
  80. package/core/built/admin/assets/{chunk.481.6c7395a49484bb4583aa.js → chunk.873.b8fc05a8ab877fb819ed.js} +62 -60
  81. package/core/built/admin/assets/code-editor-view-Dq1rQ4tS.js +1 -0
  82. package/core/built/admin/assets/color-picker-field-BonBgxhU.js +1 -0
  83. package/core/built/admin/assets/color-utils-BsrwXuDz.js +1 -0
  84. package/core/built/admin/assets/combobox-uX5r_paI.js +1 -0
  85. package/core/built/admin/assets/command-DxwBO2pF.js +1 -0
  86. package/core/built/admin/assets/comments-Dn-RO-nU.js +1 -0
  87. package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
  88. package/core/built/admin/assets/confirmation-modal-B6D6mk6R.js +1 -0
  89. package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
  90. package/core/built/admin/assets/content-api-modal-BxQ-7T6u.js +1 -0
  91. package/core/built/admin/assets/copy-field-BAxL4Zqn.js +1 -0
  92. package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
  93. package/core/built/admin/assets/custom-integration-modal---d_l7lc.js +1 -0
  94. package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
  95. package/core/built/admin/assets/{date-range-select-BFp58EMk.js → date-range-select-PB2n1HZf.js} +1 -1
  96. package/core/built/admin/assets/debounce-Dg2fW5Rg.js +3 -0
  97. package/core/built/admin/assets/design-and-theme-modal-DYRT207a.js +23 -0
  98. package/core/built/admin/assets/dirty-confirm-dialog-BnDJ39ao.js +1 -0
  99. package/core/built/admin/assets/disabled-sources-indicator-iMHrkyV3.js +1 -0
  100. package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
  101. package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
  102. package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS02.js} +1 -1
  103. package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-D5vhMdXf.js} +1 -1
  104. package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
  105. package/core/built/admin/assets/dist-WeOIvSYM.js +23 -0
  106. package/core/built/admin/assets/{dist-DyViJwIy.js → dist-cFzKLG7y.js} +3 -3
  107. package/core/built/admin/assets/dist-j1xxxnY8.js +1 -0
  108. package/core/built/admin/assets/dist-myQaY0ka.js +1 -0
  109. package/core/built/admin/assets/domain-BR8FIdjD.js +1 -0
  110. package/core/built/admin/assets/{dropzone-DlK-K2DQ.js → dropzone-hNxmUvwD.js} +1 -1
  111. package/core/built/admin/assets/{edit-profile-BKT-REJw.js → edit-profile-CENrkY0l.js} +1 -1
  112. package/core/built/admin/assets/editor-B18D0lm8.js +8 -0
  113. package/core/built/admin/assets/embed-signup-form-modal-BsCYUMvQ.js +9 -0
  114. package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
  115. package/core/built/admin/assets/{explore-ClUpJZOp.js → explore-BmrnljWw.js} +1 -1
  116. package/core/built/admin/assets/{feed-DF92dUMP.js → feed-Dy4li6i8.js} +1 -1
  117. package/core/built/admin/assets/{field-Do7lieBA.js → field-Dh_lwk13.js} +1 -1
  118. package/core/built/admin/assets/filter-sources-D2zDuJH5.js +1 -0
  119. package/core/built/admin/assets/filters-3JBQcNtJ.js +1 -0
  120. package/core/built/admin/assets/filters-CFt_BDe4.js +1 -0
  121. package/core/built/admin/assets/first-promoter-modal-D6Rjxugw.js +1 -0
  122. package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
  123. package/core/built/admin/assets/{ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
  124. package/core/built/admin/assets/{ghost-c4057474943effc568c5659ccc4909d7.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +420 -398
  125. package/core/built/admin/assets/{ghost-dark-db36d795d21c61426837df40af270966.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
  126. package/core/built/admin/assets/{gift-link-modal-f9VIMNDv.js → gift-link-modal-zMtlJhLQ.js} +1 -1
  127. package/core/built/admin/assets/{gift-links-CsUhefWG.js → gift-links-CVuqLhsZ.js} +1 -1
  128. package/core/built/admin/assets/global-data-provider-B949LDXJ.js +1 -0
  129. package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
  130. package/core/built/admin/assets/{growth-CVw1My4S.js → growth-C38Xtp8D.js} +2 -2
  131. package/core/built/admin/assets/{growth-B3I6gNLm.js → growth-CFws67_i.js} +1 -1
  132. package/core/built/admin/assets/history-modal-ILAOkbxH.js +1 -0
  133. package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
  134. package/core/built/admin/assets/html-field-9a9Id_2q.js +1 -0
  135. package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
  136. package/core/built/admin/assets/images-CrQKVc9Q.js +1 -0
  137. package/core/built/admin/assets/{inbox-CFBSu4Ed.js → inbox-BrSRzyvz.js} +6 -6
  138. package/core/built/admin/assets/index-BHy2DZX-.js +67 -0
  139. package/core/built/admin/assets/index-Ds2iUoF6.css +1 -0
  140. package/core/built/admin/assets/index.es-B9tvf-t4.js +1 -0
  141. package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
  142. package/core/built/admin/assets/input-group-6vEPvX9S.js +1 -0
  143. package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
  144. package/core/built/admin/assets/integration-header-B4DyXV06.js +1 -0
  145. package/core/built/admin/assets/integrations-BT6_iJvu.js +1 -0
  146. package/core/built/admin/assets/invite-user-modal-5xDCEw90.js +1 -0
  147. package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
  148. package/core/built/admin/assets/koenig-editor-base-BWLEB9Vf.js +1 -0
  149. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9535 -9527
  150. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
  151. package/core/built/admin/assets/{koenig-lexical-f8vHRYvz.js → koenig-lexical-Cj62r_y9.js} +86 -86
  152. package/core/built/admin/assets/{kpi-card-DSap9rAy.js → kpi-card-DOUnkZcN.js} +1 -1
  153. package/core/built/admin/assets/{kpi-card-DU0rn9Cj.js → kpi-card-IHDo8Q56.js} +1 -1
  154. package/core/built/admin/assets/{kpi-helpers-Dz33AV6h.js → kpi-helpers-CjUdECAK.js} +1 -1
  155. package/core/built/admin/assets/{kpi-tabs-BtTTrKvJ.js → kpi-tabs-X3DkOYbI.js} +1 -1
  156. package/core/built/admin/assets/{label-DS8Mi6G5.js → label-Dq6QaOuS.js} +1 -1
  157. package/core/built/admin/assets/label-picker-8YlhS2U3.js +1 -0
  158. package/core/built/admin/assets/labels-DAPjKPy2.js +1 -0
  159. package/core/built/admin/assets/layout-TMj4P-vv.js +1 -0
  160. package/core/built/admin/assets/limit-modal-BRPsit_B.js +1 -0
  161. package/core/built/admin/assets/link-helpers-Bzkk68GD.js +1 -0
  162. package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
  163. package/core/built/admin/assets/member-custom-fields-CmvsuuV_.js +1 -0
  164. package/core/built/admin/assets/member-detail-DCvRrkmJ.js +1 -0
  165. package/core/built/admin/assets/members-DxbSKngD.js +11 -0
  166. package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
  167. package/core/built/admin/assets/{moderation-DySbgyUS.js → moderation-Da5lhhWh.js} +1 -1
  168. package/core/built/admin/assets/multi-select-combobox-CYdkH7KQ.js +1 -0
  169. package/core/built/admin/assets/navigation-modal-BIoTwtMy.js +1 -0
  170. package/core/built/admin/assets/newsletter-BptlVoJm.js +1 -0
  171. package/core/built/admin/assets/newsletter-detail-modal-BIvxqE0N.js +1 -0
  172. package/core/built/admin/assets/newsletter-emails-Bf8IZLsP.js +1 -0
  173. package/core/built/admin/assets/newsletters-BgrIZk0V.js +1 -0
  174. package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
  175. package/core/built/admin/assets/{note-C8kxrz6w.js → note-DYcLOmDb.js} +1 -1
  176. package/core/built/admin/assets/{notifications-CgtSfX7t.js → notifications-B2SPFIV9.js} +1 -1
  177. package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
  178. package/core/built/admin/assets/offers-container-modal-BZnZITMw.js +1 -0
  179. package/core/built/admin/assets/{onboarding-route-4qtziA9d.js → onboarding-route-DgdfxkQh.js} +1 -1
  180. package/core/built/admin/assets/overview-BYQJXSog.js +1 -0
  181. package/core/built/admin/assets/overview-Dt2_mR6U.js +1 -0
  182. package/core/built/admin/assets/{page-header-B3Wb9Jqv.js → page-header-CpmxJj5B.js} +1 -1
  183. package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
  184. package/core/built/admin/assets/pintura-modal-B4MuBuuO.js +1 -0
  185. package/core/built/admin/assets/portal-frame-BF0NBIji.js +1 -0
  186. package/core/built/admin/assets/portal-modal-DMVbm0oy.js +1 -0
  187. package/core/built/admin/assets/{post-analytics-We-ko5Cb.js → post-analytics-CXWBOGyP.js} +1 -1
  188. package/core/built/admin/assets/post-analytics-header-ChzjXVPt.js +1 -0
  189. package/core/built/admin/assets/{post-analytics-provider-B6UynTGb.js → post-analytics-provider-Cgw4U_fw.js} +1 -1
  190. package/core/built/admin/assets/{post-share-modal-DFfO93_0.js → post-share-modal-B9Vmk_XM.js} +1 -1
  191. package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
  192. package/core/built/admin/assets/preferences-BfzJmnsd.js +5 -0
  193. package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
  194. package/core/built/admin/assets/preview-modal-Dw_HtD6S.js +1 -0
  195. package/core/built/admin/assets/profile-BT5vtv9b.js +1 -0
  196. package/core/built/admin/assets/radio-group-I7ou7PoJ.js +1 -0
  197. package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
  198. package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-DSQfprxH.js} +1 -1
  199. package/core/built/admin/assets/select-D9oU1IoM.js +1 -0
  200. package/core/built/admin/assets/settings-CfZFbyu_.js +4 -0
  201. package/core/built/admin/assets/{settings-WXNeWyU1.js → settings-CpvHWix7.js} +1 -1
  202. package/core/built/admin/assets/settings-breadcrumbs-K0USP2nw.js +1 -0
  203. package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
  204. package/core/built/admin/assets/{share-modal-DsgW3wbg.js → share-modal-ClIyPzc5.js} +1 -1
  205. package/core/built/admin/assets/slack-modal-1XXqOfiw.js +1 -0
  206. package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
  207. package/core/built/admin/assets/{sort-button-Bb_FCl7e.js → sort-button-CdBsm40v.js} +1 -1
  208. package/core/built/admin/assets/{source-icon-BeaRxUn9.js → source-icon-CEYmVLXR.js} +1 -1
  209. package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
  210. package/core/built/admin/assets/{stats-Bjj_kmJ1.js → stats-D8nZCTy5.js} +1 -1
  211. package/core/built/admin/assets/{stats-view-BxPbW6K1.js → stats-view-CugFVf0g.js} +1 -1
  212. package/core/built/admin/assets/{step-1-B1e3-1F8.js → step-1-DOYjrORn.js} +1 -1
  213. package/core/built/admin/assets/{step-2-C5WaeBJo.js → step-2-vA6RsarH.js} +1 -1
  214. package/core/built/admin/assets/{step-3-B46iVPwu.js → step-3-BtIBxMId.js} +2 -2
  215. package/core/built/admin/assets/stripe-button-UFP4QRXd.js +1 -0
  216. package/core/built/admin/assets/stripe-connect-modal-C_L3uM6n.js +1 -0
  217. package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
  218. package/core/built/admin/assets/{table-CB-4_HBJ.js → table-TMCgCQK7.js} +1 -1
  219. package/core/built/admin/assets/tabs-Dd6oOKbv.js +1 -0
  220. package/core/built/admin/assets/tag-detail-D3l-UJPR.js +1 -0
  221. package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
  222. package/core/built/admin/assets/tags-WdKQGWR8.js +1 -0
  223. package/core/built/admin/assets/testimonials-modal-CqWp-xXJ.js +1 -0
  224. package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
  225. package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
  226. package/core/built/admin/assets/tier-detail-modal-BkcR2u0A.js +1 -0
  227. package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
  228. package/core/built/admin/assets/toggle-group-Guorw93z.js +1 -0
  229. package/core/built/admin/assets/{topic-filter-DAFT87tQ.js → topic-filter-CbgoWIHn.js} +1 -1
  230. package/core/built/admin/assets/transistor-modal-DuNnnpal.js +1 -0
  231. package/core/built/admin/assets/unsplash-modal-y2bVwTHH.js +1 -0
  232. package/core/built/admin/assets/{use-analytics-data-S0FTd43s.js → use-analytics-data-B-b4wNSC.js} +1 -1
  233. package/core/built/admin/assets/use-check-theme-limit-error-m09J38Mq.js +2 -0
  234. package/core/built/admin/assets/use-feature-flag-ud0Qz8vt.js +1 -0
  235. package/core/built/admin/assets/{use-filter-params-Gpoq2E1L.js → use-filter-params-BnbBDc7T.js} +1 -1
  236. package/core/built/admin/assets/use-filterable-api-iMpDZBxD.js +1 -0
  237. package/core/built/admin/assets/use-form-vRjI0Kwk.js +1 -0
  238. package/core/built/admin/assets/{use-growth-stats-CIaiHOVb.js → use-growth-stats-DPU5rt3W.js} +1 -1
  239. package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
  240. package/core/built/admin/assets/use-limiter-BkN3E4gw.js +2 -0
  241. package/core/built/admin/assets/use-pintura-config-C_z8HTG2.js +1 -0
  242. package/core/built/admin/assets/use-pintura-editor-DNgbkhGp.js +1 -0
  243. package/core/built/admin/assets/{use-post-referrers-Cq2vBPrA.js → use-post-referrers-C2mXyrp-.js} +1 -1
  244. package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
  245. package/core/built/admin/assets/{use-reply-chain-data-DBDRtGu-.js → use-reply-chain-data-DaK8q2qQ.js} +1 -1
  246. package/core/built/admin/assets/use-setting-group-YlYcoNyP.js +1 -0
  247. package/core/built/admin/assets/use-settings-navigation-Bf1g1rEh.js +1 -0
  248. package/core/built/admin/assets/{use-simple-pagination-CjYNxdwe.js → use-simple-pagination-D9ckKo5-.js} +1 -1
  249. package/core/built/admin/assets/{use-tinybird-query-falHAtuK.js → use-tinybird-query-GSa32hVw.js} +1 -1
  250. package/core/built/admin/assets/{use-tinybird-token-D7-8ND1y.js → use-tinybird-token-CKLOZFsw.js} +1 -1
  251. package/core/built/admin/assets/use-url-input-z-p0Kewm.js +5 -0
  252. package/core/built/admin/assets/user-detail-modal-BMxOA4Sv.js +1 -0
  253. package/core/built/admin/assets/{users-Dkv8NQZe.js → users-B6hIds6n.js} +1 -1
  254. package/core/built/admin/assets/validator-Cr0sMKuW.js +7 -0
  255. package/core/built/admin/assets/{virtual-list-CvnC9zF2.js → virtual-list-RHeEL05L.js} +1 -1
  256. package/core/built/admin/assets/{web-jFYWCKDn.js → web-Bw2QMO5u.js} +2 -2
  257. package/core/built/admin/assets/web-xJtcwMy4.js +1 -0
  258. package/core/built/admin/assets/zapier-modal-BP2f4-vY.js +1 -0
  259. package/core/built/admin/index.html +20 -18
  260. package/core/frontend/helpers/authors.js +1 -1
  261. package/core/frontend/helpers/link.js +3 -8
  262. package/core/frontend/helpers/tags.js +1 -1
  263. package/core/frontend/meta/author-url.js +2 -2
  264. package/core/frontend/meta/url.js +4 -4
  265. package/core/frontend/services/data/fetch-data.js +10 -5
  266. package/core/frontend/services/proxy.js +1 -1
  267. package/core/frontend/services/routing/api-adapter.js +115 -0
  268. package/core/frontend/services/routing/api-adapter.ts +157 -0
  269. package/core/frontend/services/routing/collection-router.js +5 -3
  270. package/core/frontend/services/routing/controllers/index.js +1 -1
  271. package/core/frontend/services/routing/controllers/rss.js +26 -31
  272. package/core/frontend/services/routing/controllers/rss.ts +66 -0
  273. package/core/frontend/services/routing/controllers/static.js +8 -7
  274. package/core/frontend/services/routing/events.js +20 -6
  275. package/core/frontend/services/routing/parent-router.js +17 -4
  276. package/core/frontend/services/routing/permalink-adapter.js +13 -0
  277. package/core/frontend/services/routing/permalink-adapter.ts +10 -0
  278. package/core/frontend/services/routing/registry.js +59 -33
  279. package/core/frontend/services/routing/router-manager.js +24 -22
  280. package/core/frontend/services/routing/static-routes-router.js +4 -4
  281. package/core/frontend/services/routing/taxonomy-router.js +6 -2
  282. package/core/frontend/services/sitemap/base-site-map-generator.js +1 -28
  283. package/core/frontend/services/sitemap/site-map-manager.js +22 -57
  284. package/core/server/adapters/route-settings/utils.js +10 -2
  285. package/core/server/adapters/route-settings/utils.ts +12 -2
  286. package/core/server/api/endpoints/email-post.js +1 -1
  287. package/core/server/api/endpoints/previews.js +1 -1
  288. package/core/server/api/endpoints/search-index-public.js +3 -3
  289. package/core/server/api/endpoints/search-index.js +4 -4
  290. package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -2
  291. package/core/server/api/endpoints/utils/serializers/input/comments.js +1 -1
  292. package/core/server/api/endpoints/utils/serializers/input/pages.js +3 -3
  293. package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
  294. package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
  295. package/core/server/api/endpoints/utils/serializers/input/users.js +2 -2
  296. package/core/server/api/endpoints/utils/serializers/input/utils/url.js +31 -13
  297. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +2 -2
  298. package/core/server/api/endpoints/utils/serializers/output/mappers/tags.js +2 -2
  299. package/core/server/api/endpoints/utils/serializers/output/mappers/users.js +8 -0
  300. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +18 -11
  301. 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
  302. package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
  303. package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +74 -0
  304. package/core/server/data/migrations/versions/6.57/2026-08-04-18-54-14-reset-automation-email-analytics.js +25 -0
  305. package/core/server/data/schema/commands.js +21 -5
  306. package/core/server/data/schema/fixtures/fixtures.json +2 -2
  307. package/core/server/data/schema/schema.js +25 -5
  308. package/core/server/lib/common/to-plain.ts +1 -1
  309. package/core/server/models/post.js +12 -3
  310. package/core/server/services/adapter-manager/adapter-manager.js +1 -45
  311. package/core/server/services/adapter-manager/adapter-manager.ts +2 -48
  312. package/core/server/services/adapter-manager/adapter-paths.js +24 -0
  313. package/core/server/services/adapter-manager/adapter-paths.ts +21 -0
  314. package/core/server/services/adapter-manager/base-classes.js +22 -0
  315. package/core/server/services/adapter-manager/base-classes.ts +22 -0
  316. package/core/server/services/adapter-manager/index.js +4 -27
  317. package/core/server/services/adapter-manager/index.ts +4 -31
  318. package/core/server/services/adapter-manager/utils.js +50 -0
  319. package/core/server/services/adapter-manager/utils.ts +57 -0
  320. package/core/server/services/audience-feedback/audience-feedback-controller.js +1 -1
  321. package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
  322. package/core/server/services/automations/automations-repository.ts +3 -1
  323. package/core/server/services/automations/database-automations-repository.js +33 -14
  324. package/core/server/services/automations/database-automations-repository.ts +41 -15
  325. package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
  326. package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
  327. package/core/server/services/automations/poll.js +3 -1
  328. package/core/server/services/automations/poll.ts +4 -1
  329. package/core/server/services/automations/service.js +7 -3
  330. package/core/server/services/automations/service.ts +7 -3
  331. package/core/server/services/comments/comments-service-emails.js +1 -1
  332. package/core/server/services/comments/comments-service.js +1 -1
  333. package/core/server/services/email-analytics/email-analytics-service-wrapper.js +11 -2
  334. package/core/server/services/email-analytics/email-analytics-service.js +1 -1
  335. package/core/server/services/email-service/email-service-wrapper.js +2 -1
  336. package/core/server/services/email-service/mailgun-email-provider.js +15 -0
  337. package/core/server/services/gift-links/CONTEXT.md +9 -0
  338. package/core/server/services/gifts/CONTEXT.md +55 -0
  339. package/core/server/services/gifts/README.md +28 -0
  340. package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
  341. package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
  342. package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
  343. package/core/server/services/gifts/gift-checkout-offer.js +74 -0
  344. package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
  345. package/core/server/services/gifts/gift-codec.js +63 -0
  346. package/core/server/services/gifts/gift-codec.ts +62 -0
  347. package/core/server/services/gifts/gift-controller.js +6 -31
  348. package/core/server/services/gifts/gift-controller.ts +6 -69
  349. package/core/server/services/gifts/gift-email-service.ts +3 -2
  350. package/core/server/services/gifts/gift-schema.js +34 -0
  351. package/core/server/services/gifts/gift-schema.ts +39 -0
  352. package/core/server/services/gifts/gift-service-wrapper.js +13 -11
  353. package/core/server/services/gifts/gift-service.js +265 -52
  354. package/core/server/services/gifts/gift-service.ts +431 -97
  355. package/core/server/services/gifts/gift.ts +16 -39
  356. package/core/server/services/indexnow-ping/indexnow-ping-service.js +1 -1
  357. package/core/server/services/indexnow-ping/indexnow-ping-service.ts +2 -4
  358. package/core/server/services/lib/mailgun-client.js +3 -3
  359. package/core/server/services/link-tracking/link-click-repository.js +6 -1
  360. package/core/server/services/link-tracking/link-click-tracking-service.js +12 -3
  361. package/core/server/services/media-inliner/external-media-inliner.js +12 -8
  362. package/core/server/services/member-attribution/url-translator.js +5 -7
  363. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +4 -3
  364. package/core/server/services/member-welcome-emails/service.js +79 -15
  365. package/core/server/services/members/api.js +2 -3
  366. package/core/server/services/members/import-export/import/completion-email.js +8 -4
  367. package/core/server/services/members/import-export/import/completion-email.ts +9 -4
  368. package/core/server/services/members/import-export/import/importer.js +25 -4
  369. package/core/server/services/members/import-export/import/importer.ts +27 -6
  370. package/core/server/services/members/import-export/import/row.ts +5 -1
  371. package/core/server/services/members/import-export/index.js +6 -2
  372. package/core/server/services/members/import-export/index.ts +10 -4
  373. package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
  374. package/core/server/services/members/members-api/members-api.js +13 -7
  375. package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
  376. package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
  377. package/core/server/services/members/members-api/services/payments-service.js +6 -68
  378. package/core/server/services/members-custom-fields/queries.js +4 -13
  379. package/core/server/services/members-custom-fields/queries.ts +4 -13
  380. package/core/server/services/members-custom-fields/schema.js +19 -21
  381. package/core/server/services/members-custom-fields/schema.ts +18 -20
  382. package/core/server/services/members-custom-fields/storage.js +94 -27
  383. package/core/server/services/members-custom-fields/storage.ts +114 -39
  384. package/core/server/services/members-custom-fields/values-service.js +98 -103
  385. package/core/server/services/members-custom-fields/values-service.ts +116 -114
  386. package/core/server/services/mentions/resource-service.js +1 -1
  387. package/core/server/services/mentions/service.js +2 -2
  388. package/core/server/services/oembed/oembed-service.js +44 -3
  389. package/core/server/services/posts/posts-service.js +11 -0
  390. package/core/server/services/route-settings/dynamic-routing-service.js +10 -62
  391. package/core/server/services/route-settings/route-settings-parser.js +25 -25
  392. package/core/server/services/route-settings/route-settings-parser.ts +27 -27
  393. package/core/server/services/route-settings/validation-errors.js +68 -4
  394. package/core/server/services/route-settings/validation-errors.ts +79 -4
  395. package/core/server/services/slack-ping/slack-ping-service.js +2 -2
  396. package/core/server/services/slack-ping/slack-ping-service.ts +3 -5
  397. package/core/server/services/stats/content-stats-service.js +3 -3
  398. package/core/server/services/stats/posts-stats-service.js +29 -25
  399. package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
  400. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
  401. package/core/server/services/stripe/stripe-api.js +10 -0
  402. package/core/server/services/url/config.js +90 -135
  403. package/core/server/services/url/index.js +6 -49
  404. package/core/server/services/url/lazy-find-resource.js +14 -11
  405. package/core/server/services/url/lazy-find-resource.ts +14 -11
  406. package/core/server/services/url/lazy-url-service.js +174 -94
  407. package/core/server/services/url/lazy-url-service.ts +233 -97
  408. package/core/server/services/url/routable-resources.js +62 -71
  409. package/core/server/services/url/router-filter.js +12 -4
  410. package/core/server/services/url/router-filter.ts +12 -4
  411. package/core/server/services/webhooks/index.js +1 -1
  412. package/core/server/services/webhooks/serialize.js +2 -1
  413. package/core/server/web/gift-preview/controller.js +10 -25
  414. package/core/shared/config/env/config.testing-mysql.json +1 -2
  415. package/core/shared/config/env/config.testing.json +1 -2
  416. package/core/shared/events/index.js +1 -2
  417. package/core/shared/labs.js +4 -4
  418. package/package.json +10 -10
  419. package/pnpm-lock.yaml +27 -20
  420. package/pnpm-workspace.yaml +3 -2
  421. package/tsconfig.json +1 -0
  422. package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
  423. package/core/built/admin/assets/Combination-CJG7Flp-.js +0 -41
  424. package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +0 -1
  425. package/core/built/admin/assets/automated-emails-DRy7qY-J.js +0 -7
  426. package/core/built/admin/assets/checkbox-CWa28lgO.js +0 -1
  427. package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +0 -25
  428. package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +0 -27
  429. package/core/built/admin/assets/code-editor-view-CCr0rBWS.js +0 -1
  430. package/core/built/admin/assets/comments-DyUSMUdj.js +0 -1
  431. package/core/built/admin/assets/disabled-sources-indicator-CUxMfiPa.js +0 -1
  432. package/core/built/admin/assets/dist-2Tnz2Yfq.js +0 -1
  433. package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
  434. package/core/built/admin/assets/dist-DDY6GRVz.js +0 -1
  435. package/core/built/admin/assets/dist-DG78yroX.js +0 -23
  436. package/core/built/admin/assets/domain-C0bV2UV8.js +0 -1
  437. package/core/built/admin/assets/editor-Dp865O-4.js +0 -8
  438. package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
  439. package/core/built/admin/assets/filter-sources-BV2rnpfA.js +0 -1
  440. package/core/built/admin/assets/filters-CUXJ2Nm9.js +0 -1
  441. package/core/built/admin/assets/filters-DfbIWc_l.js +0 -1
  442. package/core/built/admin/assets/index-9Cw8z3rA.css +0 -1
  443. package/core/built/admin/assets/index-BGG7NckO.js +0 -67
  444. package/core/built/admin/assets/input-group-BSgG_lVw.js +0 -1
  445. package/core/built/admin/assets/label-picker-1DvSuLA1.js +0 -1
  446. package/core/built/admin/assets/labels-CDSUt4J_.js +0 -1
  447. package/core/built/admin/assets/layout-yQEi2qDM.js +0 -1
  448. package/core/built/admin/assets/link-helpers-7EYqvvHs.js +0 -1
  449. package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +0 -1
  450. package/core/built/admin/assets/member-detail-BAq7gDaa.js +0 -1
  451. package/core/built/admin/assets/members-CQpVFJu4.js +0 -10
  452. package/core/built/admin/assets/modals-ReORvWnu.js +0 -31
  453. package/core/built/admin/assets/newsletter-CJQvtHqN.js +0 -1
  454. package/core/built/admin/assets/newsletters-B3QQXfGT.js +0 -1
  455. package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
  456. package/core/built/admin/assets/overview-Bf2sbznq.js +0 -1
  457. package/core/built/admin/assets/overview-CBfePxcr.js +0 -1
  458. package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +0 -1
  459. package/core/built/admin/assets/preferences-DJPfHEGu.js +0 -5
  460. package/core/built/admin/assets/profile-DqBQp-gT.js +0 -1
  461. package/core/built/admin/assets/select-C5_xEt5b.js +0 -1
  462. package/core/built/admin/assets/settings-BwiVIXau.js +0 -4
  463. package/core/built/admin/assets/social-urls-BQPFEk1_.js +0 -8
  464. package/core/built/admin/assets/switch-z5Y6Uzt6.js +0 -1
  465. package/core/built/admin/assets/tabs-CCEgKYzq.js +0 -1
  466. package/core/built/admin/assets/tags-Cg2VJGmG.js +0 -1
  467. package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
  468. package/core/built/admin/assets/toggle-group-b3IlxvhV.js +0 -1
  469. package/core/built/admin/assets/web-B_5GZRvH.js +0 -1
  470. package/core/server/services/gifts/gift-repository.js +0 -2
  471. package/core/server/services/gifts/gift-repository.ts +0 -29
  472. package/core/server/services/route-settings/activation-bridge.js +0 -174
  473. package/core/server/services/route-settings/activation-bridge.ts +0 -250
  474. package/core/server/services/url/local-file-cache.js +0 -75
  475. package/core/server/services/url/queue.js +0 -260
  476. package/core/server/services/url/resource.js +0 -102
  477. package/core/server/services/url/resources.js +0 -480
  478. package/core/server/services/url/url-generator.js +0 -276
  479. package/core/server/services/url/url-service-facade.js +0 -366
  480. package/core/server/services/url/url-service-facade.ts +0 -519
  481. package/core/server/services/url/url-service.js +0 -387
  482. package/core/server/services/url/urls.js +0 -140
  483. package/core/shared/events/url-resource-updated-event.js +0 -33
@@ -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 the activation bridge
163
- // reproduces validate.js output byte-for-byte.
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 the activation bridge
206
- // reproduces validate.js output byte-for-byte.
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(at: string, reason: string, help?: string): errors.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
  }
@@ -54,7 +54,7 @@ class SlackPingService {
54
54
  const blogTitle = this.settingsCache.get('title');
55
55
  // If this is a post, we want to send the link of the post
56
56
  if (hasPostProperties(post)) {
57
- message = this.urlService.facade.getUrlForResource({ ...post, type: 'posts' }, { absolute: true });
57
+ message = this.urlService.getUrlForResource({ ...post, type: 'posts' }, { absolute: true });
58
58
  title = post.title ? post.title : null;
59
59
  author = post.authors ? post.authors[0] : null;
60
60
  if (post.custom_excerpt) {
@@ -136,7 +136,7 @@ class SlackPingService {
136
136
  fields: [
137
137
  {
138
138
  title: 'Author',
139
- value: author ? `<${this.urlService.facade.getUrlForResource({ ...author, type: 'authors' }, { absolute: true })} | ${author.name}>` : null,
139
+ value: author ? `<${this.urlService.getUrlForResource({ ...author, type: 'authors' }, { absolute: true })} | ${author.name}>` : null,
140
140
  short: true
141
141
  }
142
142
  ],
@@ -9,9 +9,7 @@ type SettingsCache = {
9
9
  };
10
10
 
11
11
  type UrlService = {
12
- facade: {
13
- getUrlForResource(resource: Record<string, unknown>, options: {absolute: boolean}): string | null;
14
- };
12
+ getUrlForResource(resource: Record<string, unknown>, options: {absolute: boolean}): string | null;
15
13
  };
16
14
 
17
15
  type UrlUtils = {
@@ -126,7 +124,7 @@ export class SlackPingService {
126
124
 
127
125
  // If this is a post, we want to send the link of the post
128
126
  if (hasPostProperties(post)) {
129
- message = this.urlService.facade.getUrlForResource({...post, type: 'posts'}, {absolute: true});
127
+ message = this.urlService.getUrlForResource({...post, type: 'posts'}, {absolute: true});
130
128
  title = post.title ? post.title : null;
131
129
  author = post.authors ? post.authors[0] : null;
132
130
 
@@ -214,7 +212,7 @@ export class SlackPingService {
214
212
  fields: [
215
213
  {
216
214
  title: 'Author',
217
- value: author ? `<${this.urlService.facade.getUrlForResource({...author, type: 'authors'}, {absolute: true})} | ${author.name}>` : null,
215
+ value: author ? `<${this.urlService.getUrlForResource({...author, type: 'authors'}, {absolute: true})} | ${author.name}>` : null,
218
216
  short: true
219
217
  }
220
218
  ],
@@ -199,7 +199,7 @@ class ContentStatsService {
199
199
  }
200
200
 
201
201
  try {
202
- const resource = await this.urlService.facade.resolveUrl(pathname);
202
+ const resource = await this.urlService.resolveUrl(pathname);
203
203
 
204
204
  if (resource) {
205
205
  // resource.type is the routing-level plural form (posts/pages/tags/authors).
@@ -251,8 +251,8 @@ class ContentStatsService {
251
251
  if (this.urlService && item.pathname) {
252
252
  try {
253
253
  // Check if URL service is ready
254
- if (this.urlService.facade.hasFinished && this.urlService.facade.hasFinished()) {
255
- const resource = await this.urlService.facade.resolveUrl(item.pathname);
254
+ if (this.urlService.hasFinished && this.urlService.hasFinished()) {
255
+ const resource = await this.urlService.resolveUrl(item.pathname);
256
256
  urlExists = !!resource; // Convert to boolean
257
257
  }
258
258
  // If URL service isn't ready, we default to true (clickable)