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.
Files changed (596) hide show
  1. package/components/ghost-storage-base-3.0.0.tgz +0 -0
  2. package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
  3. package/components/tryghost-adapter-base-scheduling-0.1.1.tgz +0 -0
  4. package/components/tryghost-adapter-base-sso-0.1.1.tgz +0 -0
  5. package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
  6. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  7. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  8. package/components/tryghost-kg-converters-1.2.3.tgz +0 -0
  9. package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
  10. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  11. package/core/boot.js +9 -6
  12. package/core/built/admin/assets/Combination-CJG7Flp-.js +41 -0
  13. package/core/built/admin/assets/Growth-CSaHoSVD.js +3 -0
  14. package/core/built/admin/assets/Newsletters-C5cs6FGo.js +1 -0
  15. package/core/built/admin/assets/Overview-j4NaeUNm.js +1 -0
  16. package/core/built/admin/assets/{PolarAngleAxis-pciQ0MOy.js → PolarAngleAxis-BGjf7sBu.js} +1 -1
  17. package/core/built/admin/assets/{RadialBarChart-DWmw2PcX.js → RadialBarChart-zeFf9mBz.js} +1 -1
  18. package/core/built/admin/assets/Web-_fD935OX.js +2 -0
  19. package/core/built/admin/assets/account-migration-DLvVA5yq.js +1 -0
  20. package/core/built/admin/assets/activitypub/{account-migration-BPZG37DJ.js → account-migration-DHbZcxGM.js} +13 -13
  21. package/core/built/admin/assets/activitypub/activitypub.js +7 -7
  22. package/core/built/admin/assets/activitypub/{avatar-ZYco0ITb.js → avatar-B3DEDB1l.js} +10 -10
  23. package/core/built/admin/assets/activitypub/{avatar-flipboard-D541lc4F.js → avatar-flipboard-Cqrg0XMx.js} +3 -3
  24. package/core/built/admin/assets/activitypub/{bluesky-sharing-NOHUtHjl.js → bluesky-sharing-zfxUey_L.js} +38 -37
  25. package/core/built/admin/assets/activitypub/{chunk-KS7C4IRE-B-GOFDPZ.js → chunk-KS7C4IRE-D4HEcEwi.js} +716 -710
  26. package/core/built/admin/assets/activitypub/{content-formatters-bwrp4iEO.js → content-formatters-BpQARh28.js} +229 -181
  27. package/core/built/admin/assets/activitypub/{copy-C7m7ovHw.js → copy-D9UWIAan.js} +2 -2
  28. package/core/built/admin/assets/activitypub/{dist-B5ira32t.js → dist-Ba_QxHHl.js} +5 -5
  29. package/core/built/admin/assets/activitypub/{domain-iqE4YXwD.js → domain-CmwSZLHK.js} +33 -33
  30. package/core/built/admin/assets/activitypub/{edit-profile-B8MFYh28.js → edit-profile-CXdwNmVG.js} +46 -45
  31. package/core/built/admin/assets/activitypub/{explore-BbdKodp3.js → explore-DfbjzGM1.js} +20 -20
  32. package/core/built/admin/assets/activitypub/{feed-Cl3A5owG.js → feed-DSEBJOqf.js} +15 -15
  33. package/core/built/admin/assets/activitypub/{field-DL6WyR-c.js → field-CY7XVcx6.js} +13 -13
  34. package/core/built/admin/assets/activitypub/{hash-6G3DzwL9.js → hash-BC9Q0N4c.js} +2 -2
  35. package/core/built/admin/assets/activitypub/{inbox-YmhuBice.js → inbox-DQ4ZnC1m.js} +2 -2
  36. package/core/built/admin/assets/activitypub/inbox-DtQ0kytT.js +1221 -0
  37. package/core/built/admin/assets/activitypub/{label-DGbLjDvE.js → label-Dw96GQ7u.js} +6 -6
  38. package/core/built/admin/assets/activitypub/layout-IdVq3lRy.js +1 -0
  39. package/core/built/admin/assets/activitypub/{moderation-qnBtk1zS.js → moderation-Dg_-I-8C.js} +32 -31
  40. package/core/built/admin/assets/activitypub/{note-C7ZRoSJv.js → note-Cy1-uKMY.js} +21 -21
  41. package/core/built/admin/assets/activitypub/{notifications-DiEA4b1o.js → notifications-B_fvoLIM.js} +17 -17
  42. package/core/built/admin/assets/activitypub/{onboarding-_JVgEwgV.js → onboarding-BFDkzeR2.js} +2 -2
  43. package/core/built/admin/assets/activitypub/{onboarding-DG4zu3qM.js → onboarding-D6rcBdPe.js} +5 -5
  44. package/core/built/admin/assets/activitypub/{preferences-B3FtocnP.js → preferences-ByJseZp6.js} +1607 -1606
  45. package/core/built/admin/assets/activitypub/{profile-D8ZJfYXB.js → profile-BFtbnIjz.js} +264 -263
  46. package/core/built/admin/assets/activitypub/{reply-C1vggenw.js → reply-CpdMeAO5.js} +2 -2
  47. package/core/built/admin/assets/activitypub/{routes-BOii-V9n.js → routes-Dh3xXfce.js} +1311 -1295
  48. package/core/built/admin/assets/activitypub/{separator-1gNokN5S.js → separator-BI1FLsYM.js} +4 -4
  49. package/core/built/admin/assets/activitypub/{settings--9Sb0nbl.js → settings-DTgqJPxM.js} +14 -14
  50. package/core/built/admin/assets/activitypub/{site-fkMmqv0V.js → site-CdL9ZLqm.js} +10 -10
  51. package/core/built/admin/assets/activitypub/{step-1-QhivSMw2.js → step-1-DIDg4aHb.js} +15 -15
  52. package/core/built/admin/assets/activitypub/{step-2-pMk9YwqD.js → step-2-BfGe8BYt.js} +7 -7
  53. package/core/built/admin/assets/activitypub/{step-3-DvmVC1fo.js → step-3-mE9spAqw.js} +12 -12
  54. package/core/built/admin/assets/activitypub/{tabs-Dc0FEEYD.js → tabs-ogqwoBlb.js} +241 -237
  55. package/core/built/admin/assets/activitypub/{topic-filter-CuDyM5bD.js → topic-filter-DLHBZT93.js} +4 -4
  56. package/core/built/admin/assets/activitypub/use-activity-pub-queries-VzbN99u_.js +1957 -0
  57. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-Drx1kdAK.js → use-navigate-with-base-path-CAwXTmqH.js} +165 -175
  58. package/core/built/admin/assets/activitypub/{use-reply-chain-data-BHkCHcW5.js → use-reply-chain-data-CxonteFD.js} +8 -8
  59. package/core/built/admin/assets/activitypub/users-BLeiOTHc.js +1464 -0
  60. package/core/built/admin/assets/activitypub/{x-BHCEe-c2.js → x-C8tAnXly.js} +2 -2
  61. package/core/built/admin/assets/{audience-adiFehTD.js → audience-BKRdsFmy.js} +1 -1
  62. package/core/built/admin/assets/{automated-emails-UWM02CMr.js → automated-emails-Cn87L5Md.js} +1 -1
  63. package/core/built/admin/assets/automations-B7-y3dXt.js +1 -0
  64. package/core/built/admin/assets/automations-BP_aHamJ.js +1 -0
  65. package/core/built/admin/assets/{avatar-flipboard-Lz3F9fNT.js → avatar-flipboard-C6Kj3Awu.js} +1 -1
  66. package/core/built/admin/assets/bluesky-sharing-Bh36XPh4.js +1 -0
  67. package/core/built/admin/assets/box-BWbhXh21.js +1 -0
  68. package/core/built/admin/assets/breadcrumb-D34YWnaw.js +1 -0
  69. package/core/built/admin/assets/card-DGoDJzrn.js +1 -0
  70. package/core/built/admin/assets/{chart-ChpW-NzQ.js → chart-BuRT2Y6p.js} +14 -14
  71. package/core/built/admin/assets/chart-helpers-DbklaJw9.js +1 -0
  72. package/core/built/admin/assets/checkbox-DA4-1UiI.js +1 -0
  73. package/core/built/admin/assets/chunk.524.45e0875f41b71e46e849.js +25 -0
  74. package/core/built/admin/assets/chunk.582.51defd4b009f0de8d4d5.js +27 -0
  75. package/core/built/admin/assets/{chunk.831.0703a12e49f51cec3f3a.js → chunk.589.096f4d0bb775e5229aa9.js} +78 -85
  76. package/core/built/admin/assets/{chunk.432.c2caa0b20f03e71d1a35.js → chunk.962.b73442b07be930bb5d85.js} +2216 -3188
  77. package/core/built/admin/assets/{chunk.432.c2caa0b20f03e71d1a35.js.LICENSE.txt → chunk.962.b73442b07be930bb5d85.js.LICENSE.txt} +0 -63
  78. package/core/built/admin/assets/code-editor-view-BKBrkffZ.js +1 -0
  79. package/core/built/admin/assets/comments-CzVNIT8b.js +1 -0
  80. package/core/built/admin/assets/container-BYz2Ed39.js +1 -0
  81. package/core/built/admin/assets/data-list-16rkJYYi.js +1 -0
  82. package/core/built/admin/assets/date-range-select-B5vSsrpx.js +1 -0
  83. package/core/built/admin/assets/disabled-sources-indicator-NkOIoMue.js +1 -0
  84. package/core/built/admin/assets/{dist-CsekE4FL.js → dist-CQJY6SVY.js} +1 -1
  85. package/core/built/admin/assets/dist-DGzPJ_bO.js +4 -0
  86. package/core/built/admin/assets/{dist-BbGrW05X.js → dist-DyViJwIy.js} +1 -1
  87. package/core/built/admin/assets/dist-Ndy-PuGN.js +1 -0
  88. package/core/built/admin/assets/dist-P_ohUlTi.js +1 -0
  89. package/core/built/admin/assets/domain-B7_n5nfx.js +1 -0
  90. package/core/built/admin/assets/dropzone-DcuJEEUf.js +5 -0
  91. package/core/built/admin/assets/edit-profile-rvDGrS8J.js +1 -0
  92. package/core/built/admin/assets/editor-BCiIyFjm.js +8 -0
  93. package/core/built/admin/assets/empty-indicator-oX1u1Pkn.js +1 -0
  94. package/core/built/admin/assets/esm-CFX93H5M.js +2 -0
  95. package/core/built/admin/assets/explore-BZAZOg5X.js +1 -0
  96. package/core/built/admin/assets/feed-CFV8xDw9.js +1 -0
  97. package/core/built/admin/assets/field-BW-M2Zwp.js +1 -0
  98. package/core/built/admin/assets/{filter-sources-v39ihhhV.js → filter-sources-Fx8wiYYw.js} +1 -1
  99. package/core/built/admin/assets/filters-CGX_pf2N.js +1 -0
  100. package/core/built/admin/assets/filters-DtcXMZDU.js +1 -0
  101. package/core/built/admin/assets/framework-provider-CmtnPXIT.js +4 -0
  102. package/core/built/admin/assets/{ghost-b57f077997b739a8ae9a70772dc016cb.js → ghost-b1dce91ad3232cc259ee5fbbf9526243.js} +294 -290
  103. package/core/built/admin/assets/ghost-dark-c8d0ab7f515b2010c173e23e9b32ea8a.css +1 -0
  104. package/core/built/admin/assets/gift-link-modal-Bk8qpnMw.js +1 -0
  105. package/core/built/admin/assets/{gift-links-CY3GwiVi.js → gift-links-nBxJjZiQ.js} +1 -1
  106. package/core/built/admin/assets/growth-Ck2s3JjR.js +1 -0
  107. package/core/built/admin/assets/hooks-BApwwBE0.js +2 -0
  108. package/core/built/admin/assets/{inbox-TEISAmth.js → inbox-BRp1_aaC.js} +35 -35
  109. package/core/built/admin/assets/index-BBf502Wg.js +69 -0
  110. package/core/built/admin/assets/index-BCCZKxP2.css +1 -0
  111. package/core/built/admin/assets/inline-EMAm3okk.js +1 -0
  112. package/core/built/admin/assets/input-group-DjuYnNCd.js +1 -0
  113. package/core/built/admin/assets/{react-dom-C6aoWSfl.js → jsx-runtime-DX3WBWVO.js} +1 -1
  114. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +5 -2
  115. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +2 -2
  116. package/core/built/admin/assets/{koenig-lexical-Di7b48ED.js → koenig-lexical-CM7UFNdL.js} +2 -2
  117. package/core/built/admin/assets/kpi-card-BrGH6FsT.js +1 -0
  118. package/core/built/admin/assets/kpi-card-CIGyijjx.js +1 -0
  119. package/core/built/admin/assets/kpi-helpers-_HRRsklE.js +1 -0
  120. package/core/built/admin/assets/kpi-tabs-BSvh9eoq.js +1 -0
  121. package/core/built/admin/assets/label-BOR9XdKl.js +1 -0
  122. package/core/built/admin/assets/label-picker-BdNAWuUX.js +1 -0
  123. package/core/built/admin/assets/{labels-GbxlXWtf.js → labels-Cyayfd-e.js} +1 -1
  124. package/core/built/admin/assets/layout-Bw4GnxXp.js +1 -0
  125. package/core/built/admin/assets/limit-service-9fwD33lU.js +20 -0
  126. package/core/built/admin/assets/link-helpers-tOlxmuH5.js +1 -0
  127. package/core/built/admin/assets/list-page-B6ANtzkd.js +1 -0
  128. package/core/built/admin/assets/mailchimp-Ds_NQ04w.svg +9 -0
  129. package/core/built/admin/assets/member-custom-fields-DvFyamLu.js +1 -0
  130. package/core/built/admin/assets/member-detail-Cg2vTqvJ.js +1 -0
  131. package/core/built/admin/assets/members-DsPxYAVm.js +10 -0
  132. package/core/built/admin/assets/modals-Bf_fyvHZ.js +31 -0
  133. package/core/built/admin/assets/moderation-Dlp7M5sa.js +1 -0
  134. package/core/built/admin/assets/multi-select-combobox-CH1o8XXQ.js +1 -0
  135. package/core/built/admin/assets/newsletter-CQ5CAYUI.js +1 -0
  136. package/core/built/admin/assets/{newsletters-DZm3yLj8.js → newsletters-D1jbtV-h.js} +1 -1
  137. package/core/built/admin/assets/note-BrAZ7Z0o.js +1 -0
  138. package/core/built/admin/assets/notifications-BnbHLOPC.js +1 -0
  139. package/core/built/admin/assets/nql-lang-B7faUf8D.js +19 -0
  140. package/core/built/admin/assets/{offers-D11QtPXX.js → offers-CrY6nSRR.js} +1 -1
  141. package/core/built/admin/assets/onboarding-route-eU146kuA.js +1 -0
  142. package/core/built/admin/assets/overview-BpbaK-xu.js +1 -0
  143. package/core/built/admin/assets/page-header-C0jVxtS8.js +1 -0
  144. package/core/built/admin/assets/{pages-BnObVRrf.js → pages-BbNWFcGj.js} +1 -1
  145. package/core/built/admin/assets/pintura-CXOmq-5H.svg +15 -0
  146. package/core/built/admin/assets/post-analytics-Iw7dk1mT.js +1 -0
  147. package/core/built/admin/assets/post-analytics-header-D9pJ4iNm.js +1 -0
  148. package/core/built/admin/assets/{post-analytics-provider-DSfBCtnj.js → post-analytics-provider-BDqcfBor.js} +1 -1
  149. package/core/built/admin/assets/post-share-modal-DBfKl2GZ.js +1 -0
  150. package/core/built/admin/assets/{posts-CwH8VJvB.js → posts-EWpZIjkr.js} +1 -1
  151. package/core/built/admin/assets/preferences-D4EANGoc.js +5 -0
  152. package/core/built/admin/assets/profile-dJ44Bn_W.js +1 -0
  153. package/core/built/admin/assets/{referrers-nxR52jPI.js → referrers-BCfFYSHc.js} +1 -1
  154. package/core/built/admin/assets/router-provider-C1R4AZ2o.js +1 -0
  155. package/core/built/admin/assets/select--2uVN9_-.js +1 -0
  156. package/core/built/admin/assets/settings-Bz3RZRmE.js +1 -0
  157. package/core/built/admin/assets/settings-SD9bxIpt.js +4 -0
  158. package/core/built/admin/assets/share-modal-CkdTTMJd.js +1 -0
  159. package/core/built/admin/assets/social-urls-BvFZwYcE.js +8 -0
  160. package/core/built/admin/assets/sort-button-DZp9Y2r1.js +1 -0
  161. package/core/built/admin/assets/source-icon-CHr9qJ2Y.js +1 -0
  162. package/core/built/admin/assets/stack-xiN0eQ6Z.js +1 -0
  163. package/core/built/admin/assets/stats-DGoXZxWE.js +1 -0
  164. package/core/built/admin/assets/stats-view-D2-1L0ms.js +1 -0
  165. package/core/built/admin/assets/step-1-RdahbwyG.js +1 -0
  166. package/core/built/admin/assets/step-2-D83PeRwd.js +1 -0
  167. package/core/built/admin/assets/step-3-p_Op4wv4.js +2 -0
  168. package/core/built/admin/assets/switch-qp518gFQ.js +1 -0
  169. package/core/built/admin/assets/table-B8LgTQJZ.js +1 -0
  170. package/core/built/admin/assets/tabs-D3ZOYkM7.js +1 -0
  171. package/core/built/admin/assets/tags-B4l891z2.js +1 -0
  172. package/core/built/admin/assets/text-DwtjwGkh.js +1 -0
  173. package/core/built/admin/assets/textarea-CTlb3Uu6.js +1 -0
  174. package/core/built/admin/assets/{tiers-EghDzCKc.js → tiers-BgYLWRJv.js} +1 -1
  175. package/core/built/admin/assets/toggle-group-DNXDkAwf.js +1 -0
  176. package/core/built/admin/assets/topic-filter-DZGjEcz3.js +1 -0
  177. package/core/built/admin/assets/{use-analytics-data-0l_OHtnR.js → use-analytics-data-CaT8RirX.js} +1 -1
  178. package/core/built/admin/assets/use-filter-params-DeQ8sX4N.js +1 -0
  179. package/core/built/admin/assets/use-growth-stats-9Db2CuJe.js +1 -0
  180. package/core/built/admin/assets/{use-post-referrers-DwI31Ril.js → use-post-referrers-BV54YcQS.js} +1 -1
  181. package/core/built/admin/assets/use-reply-chain-data-DuYZGDan.js +1 -0
  182. package/core/built/admin/assets/use-simple-pagination-8fTua9Nt.js +1 -0
  183. package/core/built/admin/assets/{use-tinybird-query-DvZBBMG7.js → use-tinybird-query-Dz156HvZ.js} +1 -1
  184. package/core/built/admin/assets/{use-tinybird-token-CQtebkvU.js → use-tinybird-token-C9RUdL_n.js} +1 -1
  185. package/core/built/admin/assets/{users-Pc8IRjk9.js → users-CcSCQFcQ.js} +1 -1
  186. package/core/built/admin/assets/virtual-list-BCvP0YF-.js +1 -0
  187. package/core/built/admin/assets/web-DTZnnUGr.js +1 -0
  188. package/core/built/admin/index.html +18 -22
  189. package/core/frontend/apps/private-blogging/index.js +1 -1
  190. package/core/frontend/apps/private-blogging/lib/middleware.js +1 -1
  191. package/core/frontend/helpers/split.js +2 -1
  192. package/core/frontend/meta/asset-url.js +1 -1
  193. package/core/frontend/meta/author-image.js +1 -1
  194. package/core/frontend/meta/blog-logo.js +1 -1
  195. package/core/frontend/meta/canonical-url.js +1 -1
  196. package/core/frontend/meta/cover-image.js +1 -1
  197. package/core/frontend/meta/get-meta.js +1 -1
  198. package/core/frontend/meta/og-image.js +1 -1
  199. package/core/frontend/meta/paginated-url.js +1 -1
  200. package/core/frontend/meta/twitter-image.js +1 -1
  201. package/core/frontend/meta/url.js +1 -1
  202. package/core/frontend/services/assets-minification/admin-auth-assets.js +1 -1
  203. package/core/frontend/services/llms/handler.js +1 -1
  204. package/core/frontend/services/proxy.js +31 -2
  205. package/core/frontend/services/routing/collection-router.js +1 -1
  206. package/core/frontend/services/routing/controllers/email-post.js +1 -1
  207. package/core/frontend/services/routing/controllers/entry/markdown.js +1 -1
  208. package/core/frontend/services/routing/controllers/entry/markdown.ts +1 -1
  209. package/core/frontend/services/routing/controllers/entry.js +1 -1
  210. package/core/frontend/services/routing/controllers/entry.ts +1 -1
  211. package/core/frontend/services/routing/controllers/previews.js +6 -7
  212. package/core/frontend/services/routing/controllers/unsubscribe.js +7 -3
  213. package/core/frontend/services/routing/email-router.js +1 -1
  214. package/core/frontend/services/routing/events.js +12 -0
  215. package/core/frontend/services/routing/middleware/page-param.js +1 -1
  216. package/core/frontend/services/routing/parent-router.js +1 -1
  217. package/core/frontend/services/routing/preview-router.js +1 -1
  218. package/core/frontend/services/routing/router-manager.js +4 -8
  219. package/core/frontend/services/routing/rss-router.js +1 -1
  220. package/core/frontend/services/routing/static-pages-router.js +1 -1
  221. package/core/frontend/services/routing/static-routes-router.js +1 -1
  222. package/core/frontend/services/routing/taxonomy-router.js +1 -1
  223. package/core/frontend/services/rss/generate-feed.js +1 -1
  224. package/core/frontend/services/sitemap/base-site-map-generator.js +23 -4
  225. package/core/frontend/services/sitemap/site-map-index-generator.js +1 -1
  226. package/core/frontend/services/sitemap/site-map-manager.js +14 -8
  227. package/core/frontend/services/sitemap/utils.js +1 -1
  228. package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +1 -1
  229. package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +1 -1
  230. package/core/frontend/utils/images.js +1 -1
  231. package/core/frontend/web/middleware/admin-toolbar.js +1 -1
  232. package/core/frontend/web/middleware/handle-image-sizes.js +2 -2
  233. package/core/frontend/web/middleware/redirect-ghost-to-admin.js +1 -1
  234. package/core/frontend/web/routers/serve-favicon.js +1 -1
  235. package/core/frontend/web/routers/serve-public-file.js +1 -1
  236. package/core/frontend/web/site.js +5 -5
  237. package/core/server/adapters/cache/AdapterCacheMemoryTTL.js +2 -2
  238. package/core/server/adapters/cache/MemoryCache.js +2 -2
  239. package/core/server/adapters/cache/Redis.js +1 -1
  240. package/core/server/adapters/cache/index.js +1 -1
  241. package/core/server/adapters/lib/redis/AdapterCacheRedis.js +2 -2
  242. package/core/server/adapters/route-settings/FileStore.js +2 -8
  243. package/core/server/adapters/route-settings/FileStore.ts +1 -6
  244. package/core/server/adapters/route-settings/S3RouteSettingsStore.js +206 -0
  245. package/core/server/adapters/route-settings/S3RouteSettingsStore.ts +199 -0
  246. package/core/server/adapters/route-settings/utils.js +13 -0
  247. package/core/server/adapters/route-settings/utils.ts +7 -0
  248. package/core/server/adapters/scheduling/deps.d.ts +0 -2
  249. package/core/server/adapters/scheduling/error-capture.js +76 -0
  250. package/core/server/adapters/scheduling/error-capture.ts +91 -0
  251. package/core/server/adapters/scheduling/utils.js +18 -34
  252. package/core/server/adapters/scheduling/utils.ts +36 -0
  253. package/core/server/adapters/storage/LocalFilesStorage.js +1 -1
  254. package/core/server/adapters/storage/LocalImagesStorage.js +1 -1
  255. package/core/server/adapters/storage/LocalMediaStorage.js +1 -1
  256. package/core/server/adapters/storage/LocalStorageBase.js +3 -3
  257. package/core/server/adapters/storage/S3Storage.js +2 -2
  258. package/core/server/adapters/storage/S3Storage.ts +1 -1
  259. package/core/server/adapters/storage/utils.js +1 -1
  260. package/core/server/api/endpoints/authentication.js +1 -1
  261. package/core/server/api/endpoints/featurebase.js +1 -1
  262. package/core/server/api/endpoints/files.js +2 -2
  263. package/core/server/api/endpoints/images.js +2 -2
  264. package/core/server/api/endpoints/libraries.d.ts +0 -1
  265. package/core/server/api/endpoints/media.js +8 -5
  266. package/core/server/api/endpoints/member-custom-fields.js +4 -1
  267. package/core/server/api/endpoints/member-custom-fields.ts +4 -1
  268. package/core/server/api/endpoints/posts.js +17 -1
  269. package/core/server/api/endpoints/search-index-public.js +4 -3
  270. package/core/server/api/endpoints/search-index.js +5 -4
  271. package/core/server/api/endpoints/settings-public.js +1 -1
  272. package/core/server/api/endpoints/settings.js +3 -1
  273. package/core/server/api/endpoints/utils/serializers/input/utils/url.js +9 -1
  274. package/core/server/api/endpoints/utils/serializers/output/default.js +7 -0
  275. package/core/server/api/endpoints/utils/serializers/output/files.js +1 -1
  276. package/core/server/api/endpoints/utils/serializers/output/media.js +1 -1
  277. package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.js +7 -4
  278. package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.ts +8 -4
  279. package/core/server/api/endpoints/utils/serializers/output/members.js +16 -26
  280. package/core/server/api/endpoints/utils/serializers/output/pages.js +7 -0
  281. package/core/server/api/endpoints/utils/serializers/output/posts.js +13 -0
  282. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +1 -1
  283. package/core/server/api/endpoints/utils/validators/input/members.js +2 -52
  284. package/core/server/data/db/backup.js +1 -1
  285. package/core/server/data/importer/handlers/image.js +3 -3
  286. package/core/server/data/importer/import-manager.js +6 -4
  287. package/core/server/data/importer/importers/content-file-importer.js +2 -2
  288. package/core/server/data/migrations/utils/schema.js +4 -2
  289. package/core/server/data/migrations/versions/6.54/2026-07-19-16-59-00-add-content-import-permission.js +10 -0
  290. 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
  291. package/core/server/data/migrations/versions/6.54/2026-07-21-01-58-20-add-automation-run-steps-poller-index.js +3 -0
  292. 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
  293. package/core/server/data/migrations/versions/6.54/2026-07-22-10-41-08-add-click-tracking-to-automated-email-recipients.js +13 -0
  294. 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
  295. package/core/server/data/migrations/versions/6.54/2026-07-23-10-24-18-add-automation-action-revision-to-redirects.js +9 -0
  296. package/core/server/data/schema/commands.js +37 -3
  297. package/core/server/data/schema/fixtures/fixtures.json +7 -0
  298. package/core/server/data/schema/lib/default-index-name.js +20 -0
  299. package/core/server/data/schema/schema.js +40 -17
  300. package/core/server/data/seeders/data-generator.js +4 -2
  301. package/core/server/lib/image/image-size.js +5 -7
  302. package/core/server/lib/image/image-utils.js +2 -2
  303. package/core/server/lib/image/index.js +4 -4
  304. package/core/server/lib/lexical.js +3 -3
  305. package/core/server/lib/mobiledoc.js +2 -2
  306. package/core/server/lib/validate-password.js +1 -1
  307. package/core/server/models/action.js +9 -0
  308. package/core/server/models/base/plugins/generate-slug.js +1 -1
  309. package/core/server/models/collection.js +1 -1
  310. package/core/server/models/custom-theme-setting.js +1 -1
  311. package/core/server/models/member.js +73 -0
  312. package/core/server/models/newsletter.js +1 -1
  313. package/core/server/models/post.js +19 -5
  314. package/core/server/models/posts-meta.js +1 -1
  315. package/core/server/models/redirect.js +1 -1
  316. package/core/server/models/settings.js +1 -1
  317. package/core/server/models/snippet.js +1 -1
  318. package/core/server/models/tag.js +1 -1
  319. package/core/server/models/user.js +1 -1
  320. package/core/server/models/welcome-email-automated-email.js +1 -1
  321. package/core/server/services/activitypub/activity-pub-service-wrapper.js +1 -1
  322. package/core/server/services/adapter-manager/adapter-manager.js +5 -1
  323. package/core/server/services/adapter-manager/adapter-manager.ts +7 -2
  324. package/core/server/services/adapter-manager/index.js +18 -15
  325. package/core/server/services/adapter-manager/index.ts +23 -17
  326. package/core/server/services/adapter-manager/utils.js +124 -14
  327. package/core/server/services/adapter-manager/utils.ts +130 -14
  328. package/core/server/services/api-version-compatibility/mw-version-rewrites.js +1 -1
  329. package/core/server/services/audience-feedback/index.js +1 -1
  330. package/core/server/services/auth/passwordreset.js +1 -1
  331. package/core/server/services/auth/session/express-session.js +1 -1
  332. package/core/server/services/auth/session/index.js +2 -2
  333. package/core/server/services/auth/session/session-service.js +1 -1
  334. package/core/server/services/automations/automations-api.js +2 -19
  335. package/core/server/services/automations/automations-api.ts +2 -31
  336. package/core/server/services/automations/automations-repository.ts +15 -0
  337. package/core/server/services/automations/database-automations-repository.js +30 -2
  338. package/core/server/services/automations/database-automations-repository.ts +33 -2
  339. package/core/server/services/automations/poll.js +16 -12
  340. package/core/server/services/automations/poll.ts +17 -15
  341. package/core/server/services/automations/service.js +1 -1
  342. package/core/server/services/automations/service.ts +3 -13
  343. package/core/server/services/automations/welcome-email-automation-poll.js +2 -1
  344. package/core/server/services/comments/index.js +1 -1
  345. package/core/server/services/custom-redirects/index.js +2 -2
  346. package/core/server/services/email-analytics/email-analytics-service-wrapper.js +14 -13
  347. package/core/server/services/email-analytics/email-analytics-service.js +1 -1
  348. package/core/server/services/email-analytics/events/start-email-analytics-job-event.js +11 -15
  349. package/core/server/services/email-analytics/events/start-email-analytics-job-event.ts +15 -0
  350. package/core/server/services/email-analytics/index.js +2 -3
  351. package/core/server/services/email-analytics/index.ts +1 -2
  352. package/core/server/services/email-analytics/jobs/email-analytics-job-scheduler.js +1 -6
  353. package/core/server/services/email-analytics/jobs/email-analytics-job-scheduler.ts +0 -9
  354. package/core/server/services/email-analytics/jobs/fetch-latest/index.js +1 -1
  355. package/core/server/services/email-analytics/jobs/index.js +0 -2
  356. package/core/server/services/email-service/email-service-wrapper.js +1 -1
  357. package/core/server/services/email-service/email-service.js +20 -4
  358. package/core/server/services/explore-ping/explore-ping-service.js +24 -39
  359. package/core/server/services/explore-ping/explore-ping-service.ts +198 -0
  360. package/core/server/services/explore-ping/index.js +11 -11
  361. package/core/server/services/explore-ping/index.ts +42 -0
  362. package/core/server/services/gifts/gift-reminder-scheduler.js +1 -1
  363. package/core/server/services/gifts/gift-reminder-scheduler.ts +1 -1
  364. package/core/server/services/gifts/gift-service-wrapper.js +1 -1
  365. package/core/server/services/identity-tokens/identity-token-service-wrapper.js +1 -1
  366. package/core/server/services/indexnow-ping/constants.js +30 -0
  367. package/core/server/services/indexnow-ping/constants.ts +32 -0
  368. package/core/server/services/indexnow-ping/index.js +29 -253
  369. package/core/server/services/indexnow-ping/index.ts +37 -0
  370. package/core/server/services/indexnow-ping/indexnow-ping-service.js +229 -0
  371. package/core/server/services/indexnow-ping/indexnow-ping-service.ts +319 -0
  372. package/core/server/services/invites/index.js +1 -1
  373. package/core/server/services/lib/mailgun-client.js +3 -3
  374. package/core/server/services/link-redirection/index.js +2 -2
  375. package/core/server/services/link-redirection/link-redirect-repository.js +1 -1
  376. package/core/server/services/link-redirection/link-redirect.js +12 -13
  377. package/core/server/services/link-redirection/link-redirect.ts +22 -0
  378. package/core/server/services/link-redirection/link-redirects-service.js +1 -1
  379. package/core/server/services/link-tracking/full-post-link.js +2 -2
  380. package/core/server/services/link-tracking/index.js +1 -1
  381. package/core/server/services/mail/ghost-mailer.js +1 -1
  382. package/core/server/services/mail/index.js +1 -1
  383. package/core/server/services/media-inliner/external-media-inliner.js +2 -2
  384. package/core/server/services/media-inliner/service.js +4 -3
  385. package/core/server/services/member-attribution/index.js +1 -1
  386. package/core/server/services/member-welcome-emails/service.js +1 -1
  387. package/core/server/services/members/api.js +8 -2
  388. package/core/server/services/members/csv/index.js +10 -0
  389. package/core/server/services/members/csv/index.ts +3 -0
  390. package/core/server/services/members/csv/parse.js +94 -0
  391. package/core/server/services/members/csv/parse.ts +111 -0
  392. package/core/server/services/members/csv/types.js +2 -0
  393. package/core/server/services/members/csv/types.ts +39 -0
  394. package/core/server/services/members/csv/unparse.js +69 -0
  395. package/core/server/services/members/csv/unparse.ts +79 -0
  396. package/core/server/services/members/exporter/query.js +35 -8
  397. package/core/server/services/members/importer/config.js +35 -0
  398. package/core/server/services/members/importer/config.ts +39 -0
  399. package/core/server/services/members/importer/members-csv-importer.js +31 -13
  400. package/core/server/services/members/members-api/members-api.js +4 -2
  401. package/core/server/services/members/members-api/repositories/event-repository.js +13 -2
  402. package/core/server/services/members/members-api/services/member-bread-service.js +42 -19
  403. package/core/server/services/members/middleware.js +1 -1
  404. package/core/server/services/members/service.js +15 -2
  405. package/core/server/services/members/stripe-connect.js +1 -1
  406. package/core/server/services/members-custom-fields/actions.js +3 -0
  407. package/core/server/services/members-custom-fields/actions.ts +6 -2
  408. package/core/server/services/members-custom-fields/config.js +34 -0
  409. package/core/server/services/members-custom-fields/config.ts +38 -0
  410. package/core/server/services/members-custom-fields/definitions-service.js +156 -21
  411. package/core/server/services/members-custom-fields/definitions-service.ts +170 -25
  412. package/core/server/services/members-custom-fields/index.js +16 -3
  413. package/core/server/services/members-custom-fields/index.ts +17 -3
  414. package/core/server/services/members-custom-fields/values-service.js +56 -10
  415. package/core/server/services/members-custom-fields/values-service.ts +62 -10
  416. package/core/server/services/mentions/mention-sending-service.js +22 -2
  417. package/core/server/services/mentions/service.js +21 -5
  418. package/core/server/services/newsletters/index.js +1 -1
  419. package/core/server/services/oembed/oembed-service.js +122 -33
  420. package/core/server/services/oembed/service.js +4 -2
  421. package/core/server/services/offers/service.js +1 -1
  422. package/core/server/services/post-scheduling/index.js +4 -3
  423. package/core/server/services/post-scheduling/index.ts +4 -3
  424. package/core/server/services/post-scheduling/post-scheduling.js +1 -1
  425. package/core/server/services/post-scheduling/post-scheduling.ts +1 -1
  426. package/core/server/services/posts/post-email-handler.js +13 -6
  427. package/core/server/services/posts/post-scheduling.js +1 -1
  428. package/core/server/services/posts/posts-service-instance.js +1 -1
  429. package/core/server/services/posts/posts-service.js +2 -2
  430. package/core/server/services/posts-public/service.js +1 -1
  431. package/core/server/services/public-config/site.js +1 -1
  432. package/core/server/services/recommendations/recommendation-service-wrapper.js +1 -1
  433. package/core/server/services/recommendations/service/libraries.d.ts +0 -2
  434. package/core/server/services/route-settings/dynamic-routing-service.js +2 -3
  435. package/core/server/services/route-settings/index.js +1 -1
  436. package/core/server/services/route-settings/route-settings-parser.js +134 -124
  437. package/core/server/services/route-settings/route-settings-parser.ts +160 -125
  438. package/core/server/services/route-settings/validation-errors.js +178 -0
  439. package/core/server/services/route-settings/validation-errors.ts +196 -0
  440. package/core/server/services/settings/settings-service.js +2 -2
  441. package/core/server/services/settings-helpers/index.js +1 -1
  442. package/core/server/services/slack-notifications/service.js +1 -1
  443. package/core/server/services/slack-ping/constants.js +16 -0
  444. package/core/server/services/slack-ping/constants.ts +15 -0
  445. package/core/server/services/slack-ping/index.js +26 -204
  446. package/core/server/services/slack-ping/index.ts +35 -0
  447. package/core/server/services/slack-ping/slack-ping-service.js +214 -0
  448. package/core/server/services/slack-ping/slack-ping-service.ts +296 -0
  449. package/core/server/services/staff/index.js +1 -1
  450. package/core/server/services/stats/posts-stats-service.js +1 -1
  451. package/core/server/services/stats/service.js +1 -1
  452. package/core/server/services/stripe/service.js +1 -1
  453. package/core/server/services/tags-public/service.js +1 -1
  454. package/core/server/services/themes/validate.js +1 -1
  455. package/core/server/services/update-check/index.js +1 -1
  456. package/core/server/services/url/lazy-url-service.js +66 -6
  457. package/core/server/services/url/lazy-url-service.ts +73 -6
  458. package/core/server/services/url/url-generator.js +1 -1
  459. package/core/server/services/url/url-service-facade.js +68 -3
  460. package/core/server/services/url/url-service-facade.ts +79 -4
  461. package/core/server/services/url/url-service.js +1 -1
  462. package/core/server/services/url/urls.js +1 -1
  463. package/core/server/services/webhooks/index.js +19 -2
  464. package/core/server/services/webhooks/listen.js +6 -11
  465. package/core/server/services/webhooks/payload.js +1 -3
  466. package/core/server/services/webhooks/serialize.js +16 -1
  467. package/core/server/web/admin/app.js +1 -1
  468. package/core/server/web/admin/middleware/redirect-admin-urls.js +1 -1
  469. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  470. package/core/server/web/api/middleware/cors.js +1 -1
  471. package/core/server/web/api/middleware/upload.js +4 -0
  472. package/core/server/web/gift-preview/controller.js +1 -1
  473. package/core/server/web/shared/middleware/redirect-amp-urls.js +1 -1
  474. package/core/server/web/shared/middleware/uncapitalise.js +2 -2
  475. package/core/server/web/shared/middleware/url-redirects.js +1 -1
  476. package/core/shared/config/defaults.json +8 -1
  477. package/core/shared/config/overrides.json +4 -0
  478. package/core/shared/url-utils.js +19 -15
  479. package/core/shared/url-utils.ts +20 -0
  480. package/ghost.js +0 -1
  481. package/package.json +26 -31
  482. package/pnpm-lock.yaml +681 -2789
  483. package/pnpm-workspace.yaml +31 -17
  484. package/components/tryghost-adapter-base-scheduling-0.0.0.tgz +0 -0
  485. package/components/tryghost-adapter-base-sso-0.0.0.tgz +0 -0
  486. package/core/built/admin/assets/Combination--5EZGaDs.js +0 -43
  487. package/core/built/admin/assets/Growth-C0NoKcHQ.js +0 -3
  488. package/core/built/admin/assets/Newsletters-Ed8kDjdy.js +0 -1
  489. package/core/built/admin/assets/Overview-CArPgRZt.js +0 -1
  490. package/core/built/admin/assets/Web-CjjYoVM3.js +0 -2
  491. package/core/built/admin/assets/account-migration-mfa5mNI9.js +0 -1
  492. package/core/built/admin/assets/activitypub/inbox-B3rjKMTv.js +0 -1221
  493. package/core/built/admin/assets/activitypub/layout-jcurg3rU.js +0 -1
  494. package/core/built/admin/assets/activitypub/use-activity-pub-queries-yqGRcnLn.js +0 -2569
  495. package/core/built/admin/assets/activitypub/users-CfyHIsTG.js +0 -1316
  496. package/core/built/admin/assets/automations-DkUnxNdv.js +0 -1
  497. package/core/built/admin/assets/automations-vjXofmV_.js +0 -1
  498. package/core/built/admin/assets/bluesky-sharing-T-pO3RcP.js +0 -1
  499. package/core/built/admin/assets/breadcrumb-CekPYTCn.js +0 -1
  500. package/core/built/admin/assets/button--i2uMDDn.js +0 -4
  501. package/core/built/admin/assets/card-CSWz_BXq.js +0 -1
  502. package/core/built/admin/assets/chart-helpers-C_4Ka_vT.js +0 -1
  503. package/core/built/admin/assets/chunk.524.8ce2c741473e2f0929dc.js +0 -25
  504. package/core/built/admin/assets/chunk.582.453459d6228d2955408b.js +0 -27
  505. package/core/built/admin/assets/code-editor-view-BfkwQto2.js +0 -1
  506. package/core/built/admin/assets/command-K75Jzs0G.js +0 -1
  507. package/core/built/admin/assets/comments-BEIi1DH-.js +0 -1
  508. package/core/built/admin/assets/container-BNXvlcjA.js +0 -1
  509. package/core/built/admin/assets/data-list-BmK5QMab.js +0 -1
  510. package/core/built/admin/assets/date-range-select-5sKzwbt7.js +0 -1
  511. package/core/built/admin/assets/disabled-sources-indicator-L_ssJTNr.js +0 -1
  512. package/core/built/admin/assets/dist-BBIJuyPg.js +0 -1
  513. package/core/built/admin/assets/dist-BHE0dp6o.js +0 -1
  514. package/core/built/admin/assets/dist-DJ6c4_De.js +0 -1
  515. package/core/built/admin/assets/dist-DW0QXQO3.js +0 -1
  516. package/core/built/admin/assets/dist-DxFLceO5.js +0 -1
  517. package/core/built/admin/assets/dist-QcACAOAD.js +0 -1
  518. package/core/built/admin/assets/dist-duxRDcQu.js +0 -177
  519. package/core/built/admin/assets/domain-CLaPZ9Rc.js +0 -1
  520. package/core/built/admin/assets/dropzone-E0bSX4_a.js +0 -5
  521. package/core/built/admin/assets/edit-profile-DqNPKaA4.js +0 -1
  522. package/core/built/admin/assets/editor-CkLXQDRp.js +0 -8
  523. package/core/built/admin/assets/empty-indicator-BTtOlgIo.js +0 -1
  524. package/core/built/admin/assets/esm-TSfGW7nz.js +0 -2
  525. package/core/built/admin/assets/explore-BEstLxKr.js +0 -1
  526. package/core/built/admin/assets/feed-C58p2voG.js +0 -1
  527. package/core/built/admin/assets/field-BSbDUHPv.js +0 -1
  528. package/core/built/admin/assets/filters-BgYv8Gt7.js +0 -1
  529. package/core/built/admin/assets/filters-DvenFkmK.js +0 -1
  530. package/core/built/admin/assets/framework-provider-jQktxm9P.js +0 -4
  531. package/core/built/admin/assets/ghost-dark-8590dea6cd695ff90f27d262c4464538.css +0 -1
  532. package/core/built/admin/assets/gift-link-modal-BPjXkEFt.js +0 -1
  533. package/core/built/admin/assets/growth-D4JNiVTe.js +0 -1
  534. package/core/built/admin/assets/hooks-Cjxexi_I.js +0 -2
  535. package/core/built/admin/assets/index-BacwlcOy.js +0 -67
  536. package/core/built/admin/assets/index-DxRNHH0o.css +0 -1
  537. package/core/built/admin/assets/inline-C22rnPro.js +0 -1
  538. package/core/built/admin/assets/input-group-CKfrILLP.js +0 -1
  539. package/core/built/admin/assets/jsx-runtime-CtMZ-PAS.js +0 -1
  540. package/core/built/admin/assets/kpi-card-DZxQKAIb.js +0 -1
  541. package/core/built/admin/assets/kpi-card-FidQ5zg3.js +0 -1
  542. package/core/built/admin/assets/kpi-helpers-C5T8Vaf1.js +0 -1
  543. package/core/built/admin/assets/kpi-tabs-_VRK6hGV.js +0 -1
  544. package/core/built/admin/assets/label-Cza7hPgn.js +0 -1
  545. package/core/built/admin/assets/label-picker-DgC_zLaw.js +0 -1
  546. package/core/built/admin/assets/layout-BxsXnKFX.js +0 -1
  547. package/core/built/admin/assets/limit-service-CTurluJQ.js +0 -20
  548. package/core/built/admin/assets/link-helpers-C3Gqy2cz.js +0 -1
  549. package/core/built/admin/assets/list-page-BtqCnow_.js +0 -1
  550. package/core/built/admin/assets/member-custom-fields-9uXFBqSt.js +0 -1
  551. package/core/built/admin/assets/member-detail-Dq6ZDIxo.js +0 -1
  552. package/core/built/admin/assets/members-CUWD9jal.js +0 -10
  553. package/core/built/admin/assets/modals-CIdwHVga.js +0 -31
  554. package/core/built/admin/assets/moderation-DTt04UDA.js +0 -1
  555. package/core/built/admin/assets/newsletter-cC8vcc9h.js +0 -1
  556. package/core/built/admin/assets/note-DZU0IY9e.js +0 -1
  557. package/core/built/admin/assets/notifications-NoHpBHJ4.js +0 -1
  558. package/core/built/admin/assets/nql-lang-CvCSOQG3.js +0 -19
  559. package/core/built/admin/assets/onboarding-route-Deg2hNm8.js +0 -1
  560. package/core/built/admin/assets/overview-BQPt_9w3.js +0 -1
  561. package/core/built/admin/assets/page-header-D6bEqPo2.js +0 -1
  562. package/core/built/admin/assets/post-analytics-header-BPw_B2_D.js +0 -1
  563. package/core/built/admin/assets/post-analytics-kp2_--XO.js +0 -1
  564. package/core/built/admin/assets/post-share-modal-Cave3Z4W.js +0 -1
  565. package/core/built/admin/assets/preferences-C7h3j_uy.js +0 -5
  566. package/core/built/admin/assets/profile-B2x4q2oB.js +0 -1
  567. package/core/built/admin/assets/router-provider-5fsX1CYn.js +0 -1
  568. package/core/built/admin/assets/select-PhPjIBBT.js +0 -1
  569. package/core/built/admin/assets/settings-D5P9Gwgo.js +0 -6
  570. package/core/built/admin/assets/settings-zUOzQV5I.js +0 -1
  571. package/core/built/admin/assets/share-modal-EfpfbuWF.js +0 -1
  572. package/core/built/admin/assets/social-urls-DgAF08Xc.js +0 -18
  573. package/core/built/admin/assets/sort-button-WOQYJm2H.js +0 -1
  574. package/core/built/admin/assets/source-icon-hm8Cj-Gx.js +0 -1
  575. package/core/built/admin/assets/stats-CnQDb62m.js +0 -1
  576. package/core/built/admin/assets/stats-view-CCfTJgSe.js +0 -1
  577. package/core/built/admin/assets/step-1-BGlhY4DS.js +0 -1
  578. package/core/built/admin/assets/step-2-D3sDEyFH.js +0 -1
  579. package/core/built/admin/assets/step-3-CvZoUdsc.js +0 -2
  580. package/core/built/admin/assets/switch-CNjkOmwz.js +0 -1
  581. package/core/built/admin/assets/table-C5nZYPnW.js +0 -1
  582. package/core/built/admin/assets/tabs-dtOcExye.js +0 -1
  583. package/core/built/admin/assets/tags-Dhm0FB_g.js +0 -1
  584. package/core/built/admin/assets/textarea-CeKw3wmV.js +0 -1
  585. package/core/built/admin/assets/toggle-group-DfhZIMTW.js +0 -1
  586. package/core/built/admin/assets/topic-filter-Cxe1Il41.js +0 -1
  587. package/core/built/admin/assets/typeof-Ca-duI-z.js +0 -1
  588. package/core/built/admin/assets/use-filter-params-Bub6BqNy.js +0 -1
  589. package/core/built/admin/assets/use-growth-stats-BJORomv0.js +0 -1
  590. package/core/built/admin/assets/use-reply-chain-data-B89YjVoo.js +0 -1
  591. package/core/built/admin/assets/use-simple-pagination-BL2SYXKi.js +0 -1
  592. package/core/built/admin/assets/virtual-list-DNJOSK9I.js +0 -1
  593. package/core/built/admin/assets/web-BHUE3YQc.js +0 -1
  594. package/core/server/adapters/scheduling/scheduling-base.js +0 -5
  595. package/core/server/adapters/sso/SSOBase.js +0 -5
  596. package/core/server/adapters/storage/index.js +0 -17
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LazyUrlService = void 0;
4
4
  const debug = require('@tryghost/debug')('services:url:lazy');
5
5
  const errors = require('@tryghost/errors');
6
- const localUtils = require('../../../shared/url-utils');
6
+ const localUtils = require('../../../shared/url-utils').default;
7
7
  const { matchPermalink, toLookupParams } = require('./permalink-matcher');
8
8
  const { buildFilter, filterMatches, routerTypeOf } = require('./router-filter');
9
+ const urlConfig = require('./config');
9
10
  // Per-resource-type base filters, mirroring eager's resource-fetch filters
10
11
  // (`modelOptions.filter` in services/url/config.js). Deliberately duplicated
11
12
  // rather than imported: lazy is meant to replace eager, at which point eager's
@@ -32,6 +33,26 @@ function buildBaseFilters() {
32
33
  }
33
34
  return baseFilters;
34
35
  }
36
+ // Columns eager drops from its in-memory URL cache (the `exclude` lists in
37
+ // services/url/config.js). Eager evaluates a router's collection filter against
38
+ // that reduced cached record, so an excluded column reads as absent — NQL then
39
+ // treats it as null. Lazy loads full records and would see the real value, so
40
+ // to preserve eager's behaviour it strips these columns before evaluating
41
+ // router filters (and neither requires nor force-loads them). Keyed by
42
+ // resourceType ('posts'/'pages'/'tags'). Read from eager's config directly so
43
+ // the two can't drift; when eager is retired this derives from whatever
44
+ // replaces it. The base filter is unaffected — it runs against the full record.
45
+ function buildExcludedFilterFields() {
46
+ const excluded = new Map();
47
+ for (const entry of urlConfig) {
48
+ const exclude = entry?.modelOptions?.exclude;
49
+ if (Array.isArray(exclude)) {
50
+ excluded.set(entry.type, new Set(exclude));
51
+ }
52
+ }
53
+ return excluded;
54
+ }
55
+ const EMPTY_FIELD_SET = new Set();
35
56
  // Relation roots are loaded via getRequiredRelations (as withRelated), not as
36
57
  // scalar columns; `page`/`type` are the router-type discriminator, always set
37
58
  // on the resource. Everything else a router filter references is a scalar
@@ -76,6 +97,7 @@ class LazyUrlService {
76
97
  routerConfigs;
77
98
  requiredRelations;
78
99
  baseFilters;
100
+ excludedFilterFields;
79
101
  constructor({ urlUtils = localUtils, findResource }) {
80
102
  if (typeof findResource !== 'function') {
81
103
  throw new errors.IncorrectUsageError({
@@ -87,6 +109,7 @@ class LazyUrlService {
87
109
  this.routerConfigs = [];
88
110
  this.requiredRelations = null;
89
111
  this.baseFilters = buildBaseFilters();
112
+ this.excludedFilterFields = buildExcludedFilterFields();
90
113
  }
91
114
  onRouterAddedType(identifier, filter, resourceType, permalink) {
92
115
  debug('onRouterAddedType', identifier, resourceType, permalink, filter);
@@ -140,6 +163,7 @@ class LazyUrlService {
140
163
  // by getRequiredRelations; eager needs none of this (it looks URLs up by id).
141
164
  getRequiredFields(routerType) {
142
165
  const fields = new Set();
166
+ const excluded = this._excludedFilterFieldsFor(routerType);
143
167
  const base = this.baseFilters.get(routerType);
144
168
  if (base) {
145
169
  base.fields.forEach(field => fields.add(field));
@@ -168,7 +192,14 @@ class LazyUrlService {
168
192
  fields.add(computed);
169
193
  }
170
194
  }
171
- filterScalarFields(config.filter).forEach(field => fields.add(field));
195
+ // Skip columns eager drops from its cache: it never force-loads them
196
+ // and evaluates their filters against an absent value, so lazy must
197
+ // not require them either (see buildExcludedFilterFields).
198
+ filterScalarFields(config.filter).forEach((field) => {
199
+ if (!excluded.has(field)) {
200
+ fields.add(field);
201
+ }
202
+ });
172
203
  }
173
204
  return [...fields];
174
205
  }
@@ -181,6 +212,10 @@ class LazyUrlService {
181
212
  return this._formatNotFound(options);
182
213
  }
183
214
  const record = this._recordForFilter(resource);
215
+ // The router (collection) filter is evaluated against eager's reduced
216
+ // column set; the base filter keeps the full record (it reads status /
217
+ // type / visibility, which eager applies at query time, not in cache).
218
+ const filterRecord = this._recordForRouterFilter(record, routerType);
184
219
  // Eager only builds URLs for resources that pass the per-type base
185
220
  // filter (e.g. visibility:public tags, status:published posts), so a
186
221
  // resource failing it has no URL there and must 404 here too. Checked
@@ -197,7 +232,7 @@ class LazyUrlService {
197
232
  continue;
198
233
  }
199
234
  this._assertNotThin(config, resource, routerType);
200
- if (filterMatches(config.compiledFilter, record)) {
235
+ if (filterMatches(config.compiledFilter, filterRecord)) {
201
236
  const path = this.urlUtils.replacePermalink(config.permalink, resource);
202
237
  return this._formatPath(path, options);
203
238
  }
@@ -218,9 +253,10 @@ class LazyUrlService {
218
253
  if (!this._baseFilterMatches(routerType, record)) {
219
254
  return false;
220
255
  }
256
+ const filterRecord = this._recordForRouterFilter(record, routerType);
221
257
  // Ownership is exclusive: only the first matching router of the type
222
258
  // owns the resource, matching eager's reservation.
223
- const owner = this.routerConfigs.find(c => c.resourceType === routerType && filterMatches(c.compiledFilter, record));
259
+ const owner = this.routerConfigs.find(c => c.resourceType === routerType && filterMatches(c.compiledFilter, filterRecord));
224
260
  return !!owner && owner.identifier === routerIdentifier;
225
261
  }
226
262
  async resolveUrl(urlPath) {
@@ -252,7 +288,7 @@ class LazyUrlService {
252
288
  // The base filter is enforced upstream by findResource's query
253
289
  // scoping (visibility:public / status:published), so only the
254
290
  // router filter needs re-checking here.
255
- const record = this._recordForFilter(resource);
291
+ const record = this._recordForRouterFilter(this._recordForFilter(resource), config.resourceType);
256
292
  if (!filterMatches(config.compiledFilter, record)) {
257
293
  continue;
258
294
  }
@@ -321,6 +357,27 @@ class LazyUrlService {
321
357
  const dbType = ROUTER_TYPE_TO_DB_TYPE[record.type];
322
358
  return dbType ? { ...record, type: dbType } : record;
323
359
  }
360
+ _excludedFilterFieldsFor(routerType) {
361
+ return this.excludedFilterFields.get(routerType) ?? EMPTY_FIELD_SET;
362
+ }
363
+ // Strips the columns eager drops from its cache so a router/collection
364
+ // filter is evaluated against the same reduced shape eager uses — an
365
+ // excluded column reads as absent (→ null in NQL) rather than its real
366
+ // value. Only affects filters that reference an excluded column; every
367
+ // other filter sees an identical record.
368
+ _recordForRouterFilter(record, routerType) {
369
+ const excluded = this._excludedFilterFieldsFor(routerType);
370
+ if (excluded.size === 0) {
371
+ return record;
372
+ }
373
+ const stripped = {};
374
+ for (const key of Object.keys(record)) {
375
+ if (!excluded.has(key)) {
376
+ stripped[key] = record[key];
377
+ }
378
+ }
379
+ return stripped;
380
+ }
324
381
  _formatPath(path, options) {
325
382
  if (options.absolute) {
326
383
  return this.urlUtils.createUrl(path, options.absolute);
@@ -363,8 +420,11 @@ class LazyUrlService {
363
420
  if (/\bprimary_author\b/.test(config.filter) && r.primary_author === undefined) {
364
421
  missing.push('primary_author');
365
422
  }
423
+ // Columns eager drops from its cache are never required — its filters
424
+ // match them as absent, so a resource lacking one is not thin here.
425
+ const excluded = this._excludedFilterFieldsFor(routerType);
366
426
  for (const field of filterScalarFields(config.filter)) {
367
- if (r[field] === undefined) {
427
+ if (!excluded.has(field) && r[field] === undefined) {
368
428
  missing.push(field);
369
429
  }
370
430
  }
@@ -1,8 +1,9 @@
1
1
  const debug = require('@tryghost/debug')('services:url:lazy');
2
2
  const errors = require('@tryghost/errors');
3
- const localUtils = require('../../../shared/url-utils');
3
+ const localUtils = require('../../../shared/url-utils').default;
4
4
  const {matchPermalink, toLookupParams} = require('./permalink-matcher');
5
5
  const {buildFilter, filterMatches, routerTypeOf} = require('./router-filter');
6
+ const urlConfig = require('./config');
6
7
 
7
8
  import type {Resource, UrlOptions, LazyUrlServiceBackend} from './url-service-facade';
8
9
  import type {CompiledFilter} from './router-filter';
@@ -57,6 +58,28 @@ function buildBaseFilters(): Map<string, BaseFilter> {
57
58
  return baseFilters;
58
59
  }
59
60
 
61
+ // Columns eager drops from its in-memory URL cache (the `exclude` lists in
62
+ // services/url/config.js). Eager evaluates a router's collection filter against
63
+ // that reduced cached record, so an excluded column reads as absent — NQL then
64
+ // treats it as null. Lazy loads full records and would see the real value, so
65
+ // to preserve eager's behaviour it strips these columns before evaluating
66
+ // router filters (and neither requires nor force-loads them). Keyed by
67
+ // resourceType ('posts'/'pages'/'tags'). Read from eager's config directly so
68
+ // the two can't drift; when eager is retired this derives from whatever
69
+ // replaces it. The base filter is unaffected — it runs against the full record.
70
+ function buildExcludedFilterFields(): Map<string, Set<string>> {
71
+ const excluded = new Map<string, Set<string>>();
72
+ for (const entry of urlConfig) {
73
+ const exclude = entry?.modelOptions?.exclude;
74
+ if (Array.isArray(exclude)) {
75
+ excluded.set(entry.type, new Set(exclude));
76
+ }
77
+ }
78
+ return excluded;
79
+ }
80
+
81
+ const EMPTY_FIELD_SET: ReadonlySet<string> = new Set();
82
+
60
83
  // Relation roots are loaded via getRequiredRelations (as withRelated), not as
61
84
  // scalar columns; `page`/`type` are the router-type discriminator, always set
62
85
  // on the resource. Everything else a router filter references is a scalar
@@ -109,6 +132,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
109
132
  private routerConfigs: RouterConfig[];
110
133
  private requiredRelations: string[] | null;
111
134
  private baseFilters: Map<string, BaseFilter>;
135
+ private excludedFilterFields: Map<string, Set<string>>;
112
136
 
113
137
  constructor({urlUtils = localUtils, findResource}: LazyUrlServiceDeps) {
114
138
  if (typeof findResource !== 'function') {
@@ -121,6 +145,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
121
145
  this.routerConfigs = [];
122
146
  this.requiredRelations = null;
123
147
  this.baseFilters = buildBaseFilters();
148
+ this.excludedFilterFields = buildExcludedFilterFields();
124
149
  }
125
150
 
126
151
  onRouterAddedType(identifier: string, filter: string | null, resourceType: string, permalink: string): void {
@@ -179,6 +204,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
179
204
  // by getRequiredRelations; eager needs none of this (it looks URLs up by id).
180
205
  getRequiredFields(routerType: string): string[] {
181
206
  const fields = new Set<string>();
207
+ const excluded = this._excludedFilterFieldsFor(routerType);
182
208
  const base = this.baseFilters.get(routerType);
183
209
  if (base) {
184
210
  base.fields.forEach(field => fields.add(field));
@@ -207,7 +233,14 @@ export class LazyUrlService implements LazyUrlServiceBackend {
207
233
  fields.add(computed);
208
234
  }
209
235
  }
210
- filterScalarFields(config.filter).forEach(field => fields.add(field));
236
+ // Skip columns eager drops from its cache: it never force-loads them
237
+ // and evaluates their filters against an absent value, so lazy must
238
+ // not require them either (see buildExcludedFilterFields).
239
+ filterScalarFields(config.filter).forEach((field) => {
240
+ if (!excluded.has(field)) {
241
+ fields.add(field);
242
+ }
243
+ });
211
244
  }
212
245
  return [...fields];
213
246
  }
@@ -223,6 +256,10 @@ export class LazyUrlService implements LazyUrlServiceBackend {
223
256
  }
224
257
 
225
258
  const record = this._recordForFilter(resource);
259
+ // The router (collection) filter is evaluated against eager's reduced
260
+ // column set; the base filter keeps the full record (it reads status /
261
+ // type / visibility, which eager applies at query time, not in cache).
262
+ const filterRecord = this._recordForRouterFilter(record, routerType);
226
263
 
227
264
  // Eager only builds URLs for resources that pass the per-type base
228
265
  // filter (e.g. visibility:public tags, status:published posts), so a
@@ -241,7 +278,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
241
278
  continue;
242
279
  }
243
280
  this._assertNotThin(config, resource, routerType);
244
- if (filterMatches(config.compiledFilter, record)) {
281
+ if (filterMatches(config.compiledFilter, filterRecord)) {
245
282
  const path = this.urlUtils.replacePermalink(config.permalink, resource);
246
283
  return this._formatPath(path, options);
247
284
  }
@@ -263,10 +300,11 @@ export class LazyUrlService implements LazyUrlServiceBackend {
263
300
  if (!this._baseFilterMatches(routerType, record)) {
264
301
  return false;
265
302
  }
303
+ const filterRecord = this._recordForRouterFilter(record, routerType);
266
304
  // Ownership is exclusive: only the first matching router of the type
267
305
  // owns the resource, matching eager's reservation.
268
306
  const owner = this.routerConfigs.find(
269
- c => c.resourceType === routerType && filterMatches(c.compiledFilter, record)
307
+ c => c.resourceType === routerType && filterMatches(c.compiledFilter, filterRecord)
270
308
  );
271
309
  return !!owner && owner.identifier === routerIdentifier;
272
310
  }
@@ -299,7 +337,10 @@ export class LazyUrlService implements LazyUrlServiceBackend {
299
337
  // The base filter is enforced upstream by findResource's query
300
338
  // scoping (visibility:public / status:published), so only the
301
339
  // router filter needs re-checking here.
302
- const record = this._recordForFilter(resource as Resource);
340
+ const record = this._recordForRouterFilter(
341
+ this._recordForFilter(resource as Resource),
342
+ config.resourceType
343
+ );
303
344
  if (!filterMatches(config.compiledFilter, record)) {
304
345
  continue;
305
346
  }
@@ -374,6 +415,29 @@ export class LazyUrlService implements LazyUrlServiceBackend {
374
415
  return dbType ? {...record, type: dbType} : record;
375
416
  }
376
417
 
418
+ private _excludedFilterFieldsFor(routerType: string): ReadonlySet<string> {
419
+ return this.excludedFilterFields.get(routerType) ?? EMPTY_FIELD_SET;
420
+ }
421
+
422
+ // Strips the columns eager drops from its cache so a router/collection
423
+ // filter is evaluated against the same reduced shape eager uses — an
424
+ // excluded column reads as absent (→ null in NQL) rather than its real
425
+ // value. Only affects filters that reference an excluded column; every
426
+ // other filter sees an identical record.
427
+ private _recordForRouterFilter(record: Record<string, unknown>, routerType: string): Record<string, unknown> {
428
+ const excluded = this._excludedFilterFieldsFor(routerType);
429
+ if (excluded.size === 0) {
430
+ return record;
431
+ }
432
+ const stripped: Record<string, unknown> = {};
433
+ for (const key of Object.keys(record)) {
434
+ if (!excluded.has(key)) {
435
+ stripped[key] = record[key];
436
+ }
437
+ }
438
+ return stripped;
439
+ }
440
+
377
441
  private _formatPath(path: string, options: UrlOptions): string {
378
442
  if (options.absolute) {
379
443
  return this.urlUtils.createUrl(path, options.absolute);
@@ -418,8 +482,11 @@ export class LazyUrlService implements LazyUrlServiceBackend {
418
482
  if (/\bprimary_author\b/.test(config.filter) && r.primary_author === undefined) {
419
483
  missing.push('primary_author');
420
484
  }
485
+ // Columns eager drops from its cache are never required — its filters
486
+ // match them as absent, so a resource lacking one is not thin here.
487
+ const excluded = this._excludedFilterFieldsFor(routerType);
421
488
  for (const field of filterScalarFields(config.filter)) {
422
- if (r[field] === undefined) {
489
+ if (!excluded.has(field) && r[field] === undefined) {
423
490
  missing.push(field);
424
491
  }
425
492
  }
@@ -1,6 +1,6 @@
1
1
  const nql = require('@tryghost/nql');
2
2
  const debug = require('@tryghost/debug')('services:url:generator');
3
- const localUtils = require('../../../shared/url-utils');
3
+ const localUtils = require('../../../shared/url-utils').default;
4
4
 
5
5
  // @TODO: merge with filter plugin
6
6
  const EXPANSIONS = [{
@@ -50,7 +50,10 @@ class UrlServiceFacade {
50
50
  const url = this.urlService.getUrlByResourceId(resource.id, options);
51
51
  if (this.isComparing() && !options?.skipComparison) {
52
52
  const context = this._compareContext(resource);
53
- setImmediate(() => this._compare('getUrlForResource', url, () => this.lazyUrlService.getUrlForResource(resource, options), context));
53
+ // Snapshot: callers mutate the resource's nested objects in place
54
+ // after this returns, but the comparison runs later via setImmediate.
55
+ const snapshot = _.cloneDeep(resource);
56
+ setImmediate(() => this._compare('getUrlForResource', url, () => this.lazyUrlService.getUrlForResource(snapshot, options), context));
54
57
  }
55
58
  return url;
56
59
  }
@@ -117,7 +120,9 @@ class UrlServiceFacade {
117
120
  const owns = this.urlService.owns(routerIdentifier, resource.id);
118
121
  if (this.isComparing()) {
119
122
  const context = this._compareContext(resource, { routerIdentifier });
120
- setImmediate(() => this._compare('ownsResource', owns, () => this.lazyUrlService.ownsResource(routerIdentifier, resource), context));
123
+ // Snapshot, as in getUrlForResource.
124
+ const snapshot = _.cloneDeep(resource);
125
+ setImmediate(() => this._compare('ownsResource', owns, () => this.lazyUrlService.ownsResource(routerIdentifier, snapshot), context));
121
126
  }
122
127
  return owns;
123
128
  }
@@ -133,6 +138,7 @@ class UrlServiceFacade {
133
138
  return {
134
139
  type: resource.type,
135
140
  id: resource.id,
141
+ status: resource.status,
136
142
  resourceKeys: Object.keys(resource),
137
143
  caller: caller.stack,
138
144
  ...extra
@@ -157,7 +163,9 @@ class UrlServiceFacade {
157
163
  if (this.isComparing()) {
158
164
  // Fire-and-forget: don't await lazy so the reverse lookup adds no
159
165
  // latency for its callers; the lazy DB read runs in the background.
160
- void this._compareAsync('resolveUrl', eagerResult, () => this.lazyUrlService.resolveUrl(urlPath), { path: urlPath }, (a, b) => _.isEqual(a, b));
166
+ // Snapshot the eager result, as in getUrlForResource.
167
+ const eagerSnapshot = _.cloneDeep(eagerResult);
168
+ void this._compareAsync('resolveUrl', eagerSnapshot, () => this.lazyUrlService.resolveUrl(urlPath), { path: urlPath }, (a, b) => this._resolvesToSameResource(a, b));
161
169
  }
162
170
  return eagerResult;
163
171
  }
@@ -250,8 +258,65 @@ class UrlServiceFacade {
250
258
  }
251
259
  this._reportMismatch(method, eagerValue, lazyValue, context, isEqual);
252
260
  }
261
+ _isNotFound(value) {
262
+ return typeof value === 'string' && value.endsWith('/404/');
263
+ }
264
+ // resolveUrl's contract is which resource a path maps to, not the exact
265
+ // serialized record. Eager (raw-knex) and lazy (model.toJSON) shape the same
266
+ // DB row differently — lazy carries a `parent` key eager omits, eager keeps
267
+ // __GHOST_URL__ placeholders lazy expands, timestamps differ in precision —
268
+ // so comparing whole records reports the same resolved resource as a
269
+ // mismatch. Compare resolved identity instead.
270
+ _resolvesToSameResource(a, b) {
271
+ if (a === null || b === null) {
272
+ return a === b;
273
+ }
274
+ const ra = a;
275
+ const rb = b;
276
+ return ra.id === rb.id && ra.type === rb.type;
277
+ }
278
+ // Divergences that are not lazy regressions, so logging them only buries
279
+ // the ones that are. Each branch is a class confirmed from production
280
+ // compare data.
281
+ _isExpectedDivergence(method, eagerValue, lazyValue, context) {
282
+ // Eager leaves tags/authors with no published posts out of its URL map
283
+ // (the shouldHavePosts gate) so they resolve to /404/, while lazy has no
284
+ // cheap way to run that check and returns the real URL. Eager cache
285
+ // staleness (a tag gaining its first post after boot) looks the same.
286
+ // Whether to keep lazy's behaviour is still open, but either way it is
287
+ // not a lazy bug, so exclude it to surface the divergences that are.
288
+ if (method === 'getUrlForResource'
289
+ && (context.type === 'tags' || context.type === 'authors')
290
+ && this._isNotFound(eagerValue) && !this._isNotFound(lazyValue)) {
291
+ return true;
292
+ }
293
+ // A site's owner starts with the default `ghost-user` slug and is
294
+ // renamed during setup. The rename emits no event the eager cache
295
+ // consumes, so eager serves /author/ghost-user/ until the next boot
296
+ // while lazy has the real slug from the database.
297
+ if (method === 'getUrlForResource'
298
+ && context.type === 'authors'
299
+ && typeof eagerValue === 'string' && eagerValue.endsWith('/author/ghost-user/')
300
+ && !this._isNotFound(lazyValue)) {
301
+ return true;
302
+ }
303
+ // lazy returns /404/ where eager serves a real URL: eager cached a
304
+ // resource that is no longer routable (unpublished or deleted since,
305
+ // with no event to evict it). Suppress only when the resource is
306
+ // provably not published — a published resource lazy refuses to route
307
+ // is a real lazy bug, so that keeps logging.
308
+ if (method === 'getUrlForResource'
309
+ && this._isNotFound(lazyValue) && !this._isNotFound(eagerValue)
310
+ && typeof context.status === 'string' && context.status !== 'published') {
311
+ return true;
312
+ }
313
+ return false;
314
+ }
253
315
  _reportMismatch(method, eagerValue, lazyValue, context, isEqual) {
254
316
  if (!isEqual(eagerValue, lazyValue)) {
317
+ if (this._isExpectedDivergence(method, eagerValue, lazyValue, context)) {
318
+ return;
319
+ }
255
320
  const { caller, ...details } = context;
256
321
  const report = new errors.InternalServerError({
257
322
  message: 'URL service parity mismatch',
@@ -139,8 +139,11 @@ export class UrlServiceFacade {
139
139
  const url = this.urlService.getUrlByResourceId(resource.id, options);
140
140
  if (this.isComparing() && !options?.skipComparison) {
141
141
  const context = this._compareContext(resource);
142
+ // Snapshot: callers mutate the resource's nested objects in place
143
+ // after this returns, but the comparison runs later via setImmediate.
144
+ const snapshot = _.cloneDeep(resource);
142
145
  setImmediate(() => this._compare('getUrlForResource', url,
143
- () => this.lazyUrlService!.getUrlForResource(resource, options),
146
+ () => this.lazyUrlService!.getUrlForResource(snapshot, options),
144
147
  context));
145
148
  }
146
149
  return url;
@@ -218,8 +221,10 @@ export class UrlServiceFacade {
218
221
  const owns = this.urlService.owns(routerIdentifier, resource.id);
219
222
  if (this.isComparing()) {
220
223
  const context = this._compareContext(resource, {routerIdentifier});
224
+ // Snapshot, as in getUrlForResource.
225
+ const snapshot = _.cloneDeep(resource);
221
226
  setImmediate(() => this._compare('ownsResource', owns,
222
- () => this.lazyUrlService!.ownsResource(routerIdentifier, resource),
227
+ () => this.lazyUrlService!.ownsResource(routerIdentifier, snapshot),
223
228
  context));
224
229
  }
225
230
  return owns;
@@ -237,6 +242,7 @@ export class UrlServiceFacade {
237
242
  return {
238
243
  type: resource.type,
239
244
  id: resource.id,
245
+ status: (resource as Record<string, unknown>).status,
240
246
  resourceKeys: Object.keys(resource),
241
247
  caller: caller.stack,
242
248
  ...extra
@@ -262,10 +268,12 @@ export class UrlServiceFacade {
262
268
  if (this.isComparing()) {
263
269
  // Fire-and-forget: don't await lazy so the reverse lookup adds no
264
270
  // latency for its callers; the lazy DB read runs in the background.
265
- void this._compareAsync('resolveUrl', eagerResult,
271
+ // Snapshot the eager result, as in getUrlForResource.
272
+ const eagerSnapshot = _.cloneDeep(eagerResult);
273
+ void this._compareAsync('resolveUrl', eagerSnapshot,
266
274
  () => this.lazyUrlService!.resolveUrl(urlPath),
267
275
  {path: urlPath},
268
- (a, b) => _.isEqual(a, b));
276
+ (a, b) => this._resolvesToSameResource(a, b));
269
277
  }
270
278
  return eagerResult;
271
279
  }
@@ -377,6 +385,70 @@ export class UrlServiceFacade {
377
385
  this._reportMismatch(method, eagerValue, lazyValue, context, isEqual);
378
386
  }
379
387
 
388
+ private _isNotFound(value: unknown): boolean {
389
+ return typeof value === 'string' && value.endsWith('/404/');
390
+ }
391
+
392
+ // resolveUrl's contract is which resource a path maps to, not the exact
393
+ // serialized record. Eager (raw-knex) and lazy (model.toJSON) shape the same
394
+ // DB row differently — lazy carries a `parent` key eager omits, eager keeps
395
+ // __GHOST_URL__ placeholders lazy expands, timestamps differ in precision —
396
+ // so comparing whole records reports the same resolved resource as a
397
+ // mismatch. Compare resolved identity instead.
398
+ private _resolvesToSameResource(a: unknown, b: unknown): boolean {
399
+ if (a === null || b === null) {
400
+ return a === b;
401
+ }
402
+ const ra = a as Resource;
403
+ const rb = b as Resource;
404
+ return ra.id === rb.id && ra.type === rb.type;
405
+ }
406
+
407
+ // Divergences that are not lazy regressions, so logging them only buries
408
+ // the ones that are. Each branch is a class confirmed from production
409
+ // compare data.
410
+ private _isExpectedDivergence(
411
+ method: string,
412
+ eagerValue: unknown,
413
+ lazyValue: unknown,
414
+ context: Record<string, unknown>
415
+ ): boolean {
416
+ // Eager leaves tags/authors with no published posts out of its URL map
417
+ // (the shouldHavePosts gate) so they resolve to /404/, while lazy has no
418
+ // cheap way to run that check and returns the real URL. Eager cache
419
+ // staleness (a tag gaining its first post after boot) looks the same.
420
+ // Whether to keep lazy's behaviour is still open, but either way it is
421
+ // not a lazy bug, so exclude it to surface the divergences that are.
422
+ if (method === 'getUrlForResource'
423
+ && (context.type === 'tags' || context.type === 'authors')
424
+ && this._isNotFound(eagerValue) && !this._isNotFound(lazyValue)) {
425
+ return true;
426
+ }
427
+
428
+ // A site's owner starts with the default `ghost-user` slug and is
429
+ // renamed during setup. The rename emits no event the eager cache
430
+ // consumes, so eager serves /author/ghost-user/ until the next boot
431
+ // while lazy has the real slug from the database.
432
+ if (method === 'getUrlForResource'
433
+ && context.type === 'authors'
434
+ && typeof eagerValue === 'string' && eagerValue.endsWith('/author/ghost-user/')
435
+ && !this._isNotFound(lazyValue)) {
436
+ return true;
437
+ }
438
+
439
+ // lazy returns /404/ where eager serves a real URL: eager cached a
440
+ // resource that is no longer routable (unpublished or deleted since,
441
+ // with no event to evict it). Suppress only when the resource is
442
+ // provably not published — a published resource lazy refuses to route
443
+ // is a real lazy bug, so that keeps logging.
444
+ if (method === 'getUrlForResource'
445
+ && this._isNotFound(lazyValue) && !this._isNotFound(eagerValue)
446
+ && typeof context.status === 'string' && context.status !== 'published') {
447
+ return true;
448
+ }
449
+ return false;
450
+ }
451
+
380
452
  private _reportMismatch(
381
453
  method: string,
382
454
  eagerValue: unknown,
@@ -385,6 +457,9 @@ export class UrlServiceFacade {
385
457
  isEqual: (a: unknown, b: unknown) => boolean
386
458
  ): void {
387
459
  if (!isEqual(eagerValue, lazyValue)) {
460
+ if (this._isExpectedDivergence(method, eagerValue, lazyValue, context)) {
461
+ return;
462
+ }
388
463
  const {caller, ...details} = context;
389
464
  const report = new errors.InternalServerError({
390
465
  message: 'URL service parity mismatch',
@@ -8,7 +8,7 @@ const UrlGenerator = require('./url-generator');
8
8
  const Queue = require('./queue');
9
9
  const Urls = require('./urls');
10
10
  const Resources = require('./resources');
11
- const urlUtils = require('../../../shared/url-utils');
11
+ const urlUtils = require('../../../shared/url-utils').default;
12
12
  const resourcesConfig = require('./config');
13
13
 
14
14
  /**
@@ -1,5 +1,5 @@
1
1
  const debug = require('@tryghost/debug')('services:url:urls');
2
- const urlUtils = require('../../../shared/url-utils');
2
+ const urlUtils = require('../../../shared/url-utils').default;
3
3
  const logging = require('@tryghost/logging');
4
4
  const errors = require('@tryghost/errors');
5
5
 
@@ -1,5 +1,22 @@
1
+ // Composition root for the webhook dispatch pipeline: builds the
2
+ // serialize → payload → trigger chain and registers the model-event
3
+ // listeners. Requires are deferred until listen() runs so the model layer
4
+ // isn't loaded before boot wires it.
1
5
  module.exports = {
2
- get listen() {
3
- return require('./listen');
6
+ listen() {
7
+ const models = require('../../models');
8
+ const limitService = require('../../services/limits');
9
+ const events = require('../../lib/common/events');
10
+ const urlService = require('../url').facade;
11
+ const createSerialize = require('./serialize');
12
+ const createPayload = require('./payload');
13
+ const WebhookTrigger = require('./webhook-trigger');
14
+ const registerListeners = require('./listen');
15
+
16
+ const serialize = createSerialize({urlService});
17
+ const payload = createPayload({serialize});
18
+ const trigger = new WebhookTrigger({models, payload, limitService});
19
+
20
+ registerListeners({events, trigger});
4
21
  }
5
22
  };
@@ -1,11 +1,4 @@
1
1
  const _ = require('lodash');
2
- const limitService = require('../../services/limits');
3
- const WebhookTrigger = require('./webhook-trigger');
4
- const models = require('../../models');
5
- const payload = require('./payload');
6
-
7
- // The webhook system is fundamentally built on top of our model event system
8
- const events = require('../../lib/common/events');
9
2
 
10
3
  const WEBHOOKS = [
11
4
  'site.changed',
@@ -44,8 +37,10 @@ const WEBHOOKS = [
44
37
  'page.tag.detached'
45
38
  ];
46
39
 
47
- const listen = async () => {
48
- const webhookTrigger = new WebhookTrigger({models, payload, limitService});
40
+ // Registers the webhook dispatch listeners on the model event system. The
41
+ // trigger is built by the composition root (index.js) and injected here so
42
+ // this module owns only the event-to-trigger wiring.
43
+ const registerListeners = ({events, trigger}) => {
49
44
  _.each(WEBHOOKS, (event) => {
50
45
  // @NOTE: The early exit makes sure the listeners are only registered once.
51
46
  // During testing the "events" instance is kept around with all the
@@ -62,9 +57,9 @@ const listen = async () => {
62
57
  return;
63
58
  }
64
59
 
65
- webhookTrigger.trigger(event, model);
60
+ trigger.trigger(event, model);
66
61
  });
67
62
  });
68
63
  };
69
64
 
70
- module.exports = listen;
65
+ module.exports = registerListeners;
@@ -1,6 +1,4 @@
1
- const serialize = require('./serialize');
2
-
3
- module.exports = (event, model) => {
1
+ module.exports = ({serialize}) => (event, model) => {
4
2
  const payload = {};
5
3
 
6
4
  if (model) {