ghost 6.47.0 → 6.48.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 (372) hide show
  1. package/MigratorConfig.js +0 -1
  2. package/bin/create-migration.js +1 -1
  3. package/bin/generate-golden-email.js +0 -1
  4. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  5. package/core/boot.js +13 -7
  6. package/core/built/admin/assets/{PolarAngleAxis-CLluELUg.js → PolarAngleAxis-DufzOBEz.js} +1 -1
  7. package/core/built/admin/assets/{_baseAssignValue-Dz1iqdwD.js → _baseAssignValue-BjUm4QeA.js} +1 -1
  8. package/core/built/admin/assets/{a-large-small-BA74vHPp.js → a-large-small-D0RF_WS9.js} +1 -1
  9. package/core/built/admin/assets/{account-migration-B-NqdKuN.js → account-migration-B7n6ezrZ.js} +1 -1
  10. package/core/built/admin/assets/activitypub/{account-migration-ClmVrAXr.js → account-migration-BJeRnMCl.js} +3 -3
  11. package/core/built/admin/assets/activitypub/activitypub.js +1 -1
  12. package/core/built/admin/assets/activitypub/{at-sign-9F3qd4Nn.js → at-sign-BygVOKdj.js} +2 -2
  13. package/core/built/admin/assets/activitypub/{avatar-flipboard-jBHA13Ej.js → avatar-flipboard-CNcjI8lK.js} +2 -2
  14. package/core/built/admin/assets/activitypub/{bluesky-sharing-1irhvVtF.js → bluesky-sharing-mgBNeRfA.js} +4 -4
  15. package/core/built/admin/assets/activitypub/{copy-DXRvsGiK.js → copy-BMChXIQc.js} +2 -2
  16. package/core/built/admin/assets/activitypub/{deleted-feed-item-67VQJde4.js → deleted-feed-item-D-spb6V1.js} +2 -2
  17. package/core/built/admin/assets/activitypub/{edit-profile-BfpbEj98.js → edit-profile-CDQ3V8-c.js} +4 -4
  18. package/core/built/admin/assets/activitypub/{feed-DUrI7SPC.js → feed-Vqr3WU-V.js} +4 -4
  19. package/core/built/admin/assets/activitypub/{hash-CMtB10PN.js → hash-D7SSXVOg.js} +2 -2
  20. package/core/built/admin/assets/activitypub/{inbox-2xq8SJ7A.js → inbox-BHk5Dw27.js} +2 -2
  21. package/core/built/admin/assets/activitypub/{index-BT97xLpd.js → index-BejkeH-m.js} +2 -2
  22. package/core/built/admin/assets/activitypub/{index-DssHQAjK.js → index-BqL23J-m.js} +32 -32
  23. package/core/built/admin/assets/activitypub/{index-Ba6FXt8y.js → index-CHhvjNuG.js} +6 -6
  24. package/core/built/admin/assets/activitypub/{index-ERtz4Bvy.js → index-CZsNO9hA.js} +5 -5
  25. package/core/built/admin/assets/activitypub/{index-sgY7lLrg.js → index-CaIhRYzz.js} +3 -3
  26. package/core/built/admin/assets/activitypub/{index-D1EWoXrK.js → index-DA2MkQVu.js} +2 -2
  27. package/core/built/admin/assets/activitypub/{index-sudOvKcY.js → index-DHhFcmfu.js} +21 -21
  28. package/core/built/admin/assets/activitypub/{index-Ccighz1t.js → index-sh2bDSls.js} +4 -4
  29. package/core/built/admin/assets/activitypub/{label-KQSsSjmN.js → label-D80yhOIm.js} +2 -2
  30. package/core/built/admin/assets/activitypub/{moderation-B78if-r0.js → moderation-LJNmKBCi.js} +3 -3
  31. package/core/built/admin/assets/activitypub/{note-Bfr7ovn7.js → note-CyjKLcQ-.js} +4 -4
  32. package/core/built/admin/assets/activitypub/{reply-CehesW8Z.js → reply-BiiCFx7G.js} +2 -2
  33. package/core/built/admin/assets/activitypub/{separator-B_Ly_Qq8.js → separator-CY3PMmtI.js} +2 -2
  34. package/core/built/admin/assets/activitypub/{settings-D6R97p7v.js → settings-BQy6Gr7R.js} +3 -3
  35. package/core/built/admin/assets/activitypub/{step-1-Cl1YxgZ1.js → step-1-DCqRwgf3.js} +3 -3
  36. package/core/built/admin/assets/activitypub/{step-2-DDyR--Qe.js → step-2-DQlhehTO.js} +5 -5
  37. package/core/built/admin/assets/activitypub/{step-3-Don8gv0n.js → step-3-DAUCAcxW.js} +7 -7
  38. package/core/built/admin/assets/activitypub/{tabs-DTpfm-wu.js → tabs-B_6UuI7r.js} +3 -3
  39. package/core/built/admin/assets/activitypub/{topic-filter-Oee10885.js → topic-filter--FT1A_dc.js} +2 -2
  40. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
  41. package/core/built/admin/assets/admin-x-settings/{code-editor-view-CGZuiDGx.js → code-editor-view-Bp_sCJR0.js} +2 -2
  42. package/core/built/admin/assets/admin-x-settings/{index-CwEblQZC.js → index-BPXA3pTW.js} +5 -5
  43. package/core/built/admin/assets/admin-x-settings/{index-rr_UntGE.js → index-BS375mg1.js} +3 -3
  44. package/core/built/admin/assets/admin-x-settings/{index-BpM6W2br.js → index-B_PlQoEG.js} +3 -3
  45. package/core/built/admin/assets/admin-x-settings/{index-BGdS5xw4.js → index-BflhAbu2.js} +3 -3
  46. package/core/built/admin/assets/admin-x-settings/{index-DtWUeJhp.js → index-CftMe8fe.js} +3 -3
  47. package/core/built/admin/assets/admin-x-settings/{index-B65inCwj.js → index-DRB7FH6y.js} +2 -2
  48. package/core/built/admin/assets/admin-x-settings/{index-DSX261S9.js → index-DijZ3oAM.js} +2 -2
  49. package/core/built/admin/assets/admin-x-settings/{index-DF6YEs01.js → index-cV0Lof9H.js} +1526 -1500
  50. package/core/built/admin/assets/admin-x-settings/{index-CluZYkUC.js → index-qc4x3vZj.js} +3 -3
  51. package/core/built/admin/assets/admin-x-settings/{modals-Bf4KOU31.js → modals-CkjuEByw.js} +55 -67
  52. package/core/built/admin/assets/{arrow-right-BxP3aeA5.js → arrow-right-BF77Yx73.js} +1 -1
  53. package/core/built/admin/assets/{at-sign-Bf0y-jim.js → at-sign-DhJF3n_V.js} +1 -1
  54. package/core/built/admin/assets/{audience-DHR1WgFK.js → audience-DHPKKayV.js} +1 -1
  55. package/core/built/admin/assets/{automated-emails-DbJbDLib.js → automated-emails-DInKYTEC.js} +1 -1
  56. package/core/built/admin/assets/{automations-aLgTzSsZ.js → automations-DJpeZw9T.js} +1 -1
  57. package/core/built/admin/assets/automations-DpQKN9j0.js +1 -0
  58. package/core/built/admin/assets/{avatar-flipboard-B0F2LMW5.js → avatar-flipboard-yRdmFC6R.js} +1 -1
  59. package/core/built/admin/assets/{bluesky-sharing-DjhtIuyr.js → bluesky-sharing-qSiCERoR.js} +1 -1
  60. package/core/built/admin/assets/{chart-BGwxlD0z.js → chart-CP2N9ZC-.js} +1 -1
  61. package/core/built/admin/assets/{chevron-up-C2FeVzre.js → chevron-up-BQ6fOpFR.js} +1 -1
  62. package/core/built/admin/assets/{chunk.524.31b31f7958dfae110016.js → chunk.524.ad4560630d4d62fbceb5.js} +4 -4
  63. package/core/built/admin/assets/{chunk.582.a855020953886af6e83b.js → chunk.582.fd4878e4e0af31ef86c0.js} +6 -6
  64. package/core/built/admin/assets/{chunk.972.258e6b1dab79b2189f1c.js → chunk.98.69968e0f393d82c2949c.js} +4 -4
  65. package/core/built/admin/assets/{code-editor-view-DzPkm_nJ.js → code-editor-view-CVODiRVJ.js} +1 -1
  66. package/core/built/admin/assets/{comments-DgItXUWN.js → comments-CP-NxopD.js} +1 -1
  67. package/core/built/admin/assets/{content-helpers-BNefU_PE.js → content-helpers-Crzkusiq.js} +1 -1
  68. package/core/built/admin/assets/{copy-DoNNUZlb.js → copy-r1LFsU75.js} +1 -1
  69. package/core/built/admin/assets/{data-list-CO1cZQRU.js → data-list-CzSc7z-2.js} +1 -1
  70. package/core/built/admin/assets/{deleted-feed-item-DC2wSAhv.js → deleted-feed-item-PyEM8FiX.js} +1 -1
  71. package/core/built/admin/assets/{dropzone-BTODLafn.js → dropzone-DI8A9qO6.js} +1 -1
  72. package/core/built/admin/assets/{edit-profile-BMMpmWIZ.js → edit-profile-DlJ64lV7.js} +1 -1
  73. package/core/built/admin/assets/editor-CevNZicQ.js +8 -0
  74. package/core/built/admin/assets/{empty-indicator-Dza0FQlj.js → empty-indicator-Dxg05yWg.js} +1 -1
  75. package/core/built/admin/assets/{en-yf9qAqTu.js → en-D93GVp3o.js} +1 -1
  76. package/core/built/admin/assets/{eye-CAqnnfQF.js → eye-6POPPpTw.js} +1 -1
  77. package/core/built/admin/assets/{feed-BkNsbWiF.js → feed-P_RMYb5x.js} +1 -1
  78. package/core/built/admin/assets/{field-Ddb1ZOqM.js → field-DeNjwWrL.js} +1 -1
  79. package/core/built/admin/assets/{filter-query-core-5Mac92kh.js → filter-query-core-DFs5WSmA.js} +1 -1
  80. package/core/built/admin/assets/{filters-BCb4bgtg.js → filters-C_BSQgb7.js} +1 -1
  81. package/core/built/admin/assets/{gh-chart-C6zkIg8k.js → gh-chart-pNHAbeQn.js} +1 -1
  82. package/core/built/admin/assets/{ghost-5313258fd425ced5db6516acde01ba36.js → ghost-3f5b3569e0beec61266cb74077677fbc.js} +3 -3
  83. package/core/built/admin/assets/{growth-ezUlN8OE.js → growth-B83tVOYO.js} +1 -1
  84. package/core/built/admin/assets/{hash-DE2OmChI.js → hash-BCTATV5W.js} +1 -1
  85. package/core/built/admin/assets/{inbox-OthiHvlV.js → inbox-rE3c5suT.js} +1 -1
  86. package/core/built/admin/assets/{index-BLyzLfyx.js → index-7EiRc5Ay.js} +1 -1
  87. package/core/built/admin/assets/{index-CV5v0eFE.js → index-B5bpZGTI.js} +1 -1
  88. package/core/built/admin/assets/index-BNtzAd7H.css +1 -0
  89. package/core/built/admin/assets/{index-BC1yWeaJ.js → index-BT33w2Io.js} +1 -1
  90. package/core/built/admin/assets/{index-DjffQ3VJ.js → index-BbkXWdhb.js} +1 -1
  91. package/core/built/admin/assets/{index-DH6PA3W6.js → index-BfhSrnrJ.js} +1 -1
  92. package/core/built/admin/assets/{index-CRvK0Iac.js → index-BmR_OUpb.js} +1 -1
  93. package/core/built/admin/assets/{index-CNV7fxKZ.js → index-BuhMgn5c.js} +1 -1
  94. package/core/built/admin/assets/{index-pkixJbdq.js → index-C-075SzJ.js} +34 -34
  95. package/core/built/admin/assets/{index-DJoByVS8.js → index-CB3hJd9E.js} +1 -1
  96. package/core/built/admin/assets/{index-D3bb-MEJ.js → index-CCISLIQd.js} +1 -1
  97. package/core/built/admin/assets/{index-BIEhcKiV.js → index-Cnptpx11.js} +1 -1
  98. package/core/built/admin/assets/{index-DK1cu59e.js → index-CrBRU81r.js} +1 -1
  99. package/core/built/admin/assets/{index-B0_p1-R0.js → index-CscUHFJp.js} +1 -1
  100. package/core/built/admin/assets/{index-Bi5qzf86.js → index-D3ZzfuJt.js} +1 -1
  101. package/core/built/admin/assets/{index-CKPny_6u.js → index-D4wn-U0V.js} +1 -1
  102. package/core/built/admin/assets/{index-YzcsD17L.js → index-DNRlkUeu.js} +1 -1
  103. package/core/built/admin/assets/{index-BoMsDCxD.js → index-D_G6negZ.js} +1 -1
  104. package/core/built/admin/assets/{index-CwsmINtV.js → index-DfxHSK1s.js} +1 -1
  105. package/core/built/admin/assets/{index-X91I8PcS.js → index-Dpc3COTl.js} +1 -1
  106. package/core/built/admin/assets/{index-c1h-EQpd.js → index-UsbnU2jy.js} +1 -1
  107. package/core/built/admin/assets/{index-oKgNVmwC.js → index-VfrJzh5m.js} +1 -1
  108. package/core/built/admin/assets/{index-D5MrQBSe.js → index-fbxc8Dpt.js} +1 -1
  109. package/core/built/admin/assets/{input-group-BOLMrDfJ.js → input-group-BLygwiDK.js} +1 -1
  110. package/core/built/admin/assets/{koenig-lexical-DlzcWUPw.js → koenig-lexical-B9U3sBS5.js} +1 -1
  111. package/core/built/admin/assets/{kpi-card-BlF7JKRC.js → kpi-card-Bmnvvp4X.js} +1 -1
  112. package/core/built/admin/assets/{kpi-card-HDshm3lO.js → kpi-card-CGVtzdgJ.js} +1 -1
  113. package/core/built/admin/assets/{kpi-tabs-Dty6zMSP.js → kpi-tabs-DxbvMkIU.js} +1 -1
  114. package/core/built/admin/assets/{kpis-CFz4r5wW.js → kpis-CQBAGVjP.js} +1 -1
  115. package/core/built/admin/assets/{label-CVzNcbjb.js → label-DpTwHQi_.js} +1 -1
  116. package/core/built/admin/assets/{links-D9dHwDlP.js → links-Bdw25FJF.js} +1 -1
  117. package/core/built/admin/assets/{list-page-shoQED7N.js → list-page-BvTQwP5F.js} +1 -1
  118. package/core/built/admin/assets/{loader-circle-cad701gH.js → loader-circle-3HJkQKWf.js} +1 -1
  119. package/core/built/admin/assets/{mail-DG4PKpZy.js → mail-DvrqP73n.js} +1 -1
  120. package/core/built/admin/assets/{main-layout-BmFla3Se.js → main-layout-BAPpm0bz.js} +1 -1
  121. package/core/built/admin/assets/{members-d19Z6k1C.js → members-CF4CzJSf.js} +1 -1
  122. package/core/built/admin/assets/minus-DJIePlnU.js +1 -0
  123. package/core/built/admin/assets/modals-CVsVi0zu.js +35 -0
  124. package/core/built/admin/assets/{moderation-BWBLnh4h.js → moderation-BX1KH5QL.js} +1 -1
  125. package/core/built/admin/assets/{newsletter-DNY4MroI.js → newsletter-BlKeb58e.js} +1 -1
  126. package/core/built/admin/assets/{newsletters-B10iBPP-.js → newsletters-3In8H0Z5.js} +1 -1
  127. package/core/built/admin/assets/{note-S0b6wjRR.js → note-ybwJCAxm.js} +1 -1
  128. package/core/built/admin/assets/{offers-B-9mZqKd.js → offers-Cl0ZNEMr.js} +1 -1
  129. package/core/built/admin/assets/{onboarding-route-AMFn0X5G.js → onboarding-route-A3ECeMAi.js} +1 -1
  130. package/core/built/admin/assets/{overview-WKadyZRW.js → overview-DrMrfsk-.js} +1 -1
  131. package/core/built/admin/assets/{pagemenu-BofQcFaF.js → pagemenu-O7HbsehH.js} +1 -1
  132. package/core/built/admin/assets/{pencil-LfMcD0vS.js → pencil-CknTbJeE.js} +1 -1
  133. package/core/built/admin/assets/{pin-CayUjL8e.js → pin-BNs-JdQ3.js} +1 -1
  134. package/core/built/admin/assets/{post-analytics-DMHvt3Jl.js → post-analytics-Dk1VloZp.js} +1 -1
  135. package/core/built/admin/assets/{post-analytics-context-10cqioP1.js → post-analytics-context-kPL94Ivz.js} +1 -1
  136. package/core/built/admin/assets/{post-analytics-header-DlICE18K.js → post-analytics-header-CpMOozq-.js} +1 -1
  137. package/core/built/admin/assets/{post-share-modal-Cfwy2UwD.js → post-share-modal-F0dobdhF.js} +1 -1
  138. package/core/built/admin/assets/posts/{alert-dialog-cQK32v9G.js → alert-dialog-NoPLULhG.js} +5 -5
  139. package/core/built/admin/assets/posts/{app-utils-DQ2hixop.js → app-utils-BiM9QO4L.js} +3 -3
  140. package/core/built/admin/assets/posts/{automations-BqkU9-zY.js → automations-BgIAmq9f.js} +3 -3
  141. package/core/built/admin/assets/posts/{automations-CoilKpVH.js → automations-D_XwO405.js} +30 -30
  142. package/core/built/admin/assets/posts/{avatar-BejAO6Rj.js → avatar-BYsYOpmq.js} +5 -5
  143. package/core/built/admin/assets/posts/{badge-BYGRyd2f.js → badge-B54vLono.js} +2 -2
  144. package/core/built/admin/assets/posts/{button-CG2Dpjqy.js → button-DMLfwOQt.js} +3 -3
  145. package/core/built/admin/assets/posts/{check-fsC5BvGC.js → check-D6qDi62T.js} +2 -2
  146. package/core/built/admin/assets/posts/{comments-CsyF5C1S.js → comments-CyYCbvUG.js} +26 -26
  147. package/core/built/admin/assets/posts/{data-list-JslRF2F5.js → data-list-B0DrMm-x.js} +4 -4
  148. package/core/built/admin/assets/posts/{dialog-3KY3A8t5.js → dialog-DtEZpY1q.js} +3 -3
  149. package/core/built/admin/assets/posts/{editor-3HPW_xW4.js → editor-LJHLmUoW.js} +1943 -1943
  150. package/core/built/admin/assets/posts/{ellipsis-BhK5S70U.js → ellipsis-JeIF0Biz.js} +2 -2
  151. package/core/built/admin/assets/posts/{empty-indicator-C9DbE9wZ.js → empty-indicator-dktzN32y.js} +2 -2
  152. package/core/built/admin/assets/posts/{filters-BCBDCnQF.js → filters-D1L-LU_m.js} +10 -10
  153. package/core/built/admin/assets/posts/{get-site-timezone-83C41MGz.js → get-site-timezone-dsyl8nlB.js} +3 -3
  154. package/core/built/admin/assets/posts/{growth-COoL1kMe.js → growth-yX8UB_ce.js} +12 -12
  155. package/core/built/admin/assets/posts/{heading-D3VqNO3z.js → heading-CNte43wH.js} +2 -2
  156. package/core/built/admin/assets/posts/{hooks-5YpJYWOw.js → hooks-BIgv7QBp.js} +2 -2
  157. package/core/built/admin/assets/posts/{index-Dqn5WIFh.js → index-2FO6Vy0r.js} +11 -11
  158. package/core/built/admin/assets/posts/{input-C1qcZBGw.js → input-BycmlCp8.js} +3 -3
  159. package/core/built/admin/assets/posts/{input-surface-C4vUAdbV.js → input-surface-vY3ACaso.js} +2 -2
  160. package/core/built/admin/assets/posts/{koenig-lexical-NZjxFY0X.js → koenig-lexical-Cq8SUcZC.js} +2 -2
  161. package/core/built/admin/assets/posts/{kpis-C2FEXoUT.js → kpis-CAJcU1w0.js} +27 -27
  162. package/core/built/admin/assets/posts/{label-CmJzmJY7.js → label-BmLTflMF.js} +3 -3
  163. package/core/built/admin/assets/posts/{links-nzud7t_t.js → links-BVcmMZmF.js} +4 -4
  164. package/core/built/admin/assets/posts/{list-page-D_rv4TgN.js → list-page-DX2WRn5T.js} +4 -4
  165. package/core/built/admin/assets/posts/{loading-indicator-DAOD6O2b.js → loading-indicator-C6FD4nS6.js} +3 -3
  166. package/core/built/admin/assets/posts/{mail-CtH0gTpq.js → mail-BMyoNUVM.js} +2 -2
  167. package/core/built/admin/assets/posts/{main-layout-VqvCxOXQ.js → main-layout-ClnxdW1Y.js} +2 -2
  168. package/core/built/admin/assets/posts/{message-circle-D4bCjxKS.js → message-circle-DwfHX-p1.js} +2 -2
  169. package/core/built/admin/assets/posts/{newsletter-BfwFyv6d.js → newsletter-Bid5_Bim.js} +19 -19
  170. package/core/built/admin/assets/posts/{overview-DKGum6ro.js → overview-BXti6Wg_.js} +18 -18
  171. package/core/built/admin/assets/posts/{pencil-Cffx-vm3.js → pencil-ChTk8IpW.js} +2 -2
  172. package/core/built/admin/assets/posts/{plus-D2ekwsq0.js → plus-DIKls8C6.js} +2 -2
  173. package/core/built/admin/assets/posts/{popover-DdbdKflP.js → popover-DVZsAT5u.js} +4 -4
  174. package/core/built/admin/assets/posts/{post-analytics-BleJYoNZ.js → post-analytics-DBL_kS0g.js} +6 -6
  175. package/core/built/admin/assets/posts/{post-analytics-context-DT3Avn9Q.js → post-analytics-context-Dr5y5nxd.js} +6 -6
  176. package/core/built/admin/assets/posts/{post-analytics-header-BktlrM_v.js → post-analytics-header-LH2-Jzgp.js} +13 -13
  177. package/core/built/admin/assets/posts/{post-share-modal-G8v3ZuSg.js → post-share-modal-D4DrfNfJ.js} +6 -6
  178. package/core/built/admin/assets/posts/{posts-CdWV_nlm.js → posts-ByxhcCrn.js} +2 -2
  179. package/core/built/admin/assets/posts/posts.js +1 -1
  180. package/core/built/admin/assets/posts/{search-CXP00fG8.js → search-DZR23LP-.js} +2 -2
  181. package/core/built/admin/assets/posts/{settings-DEXxDYc2.js → settings-CWyOKKnK.js} +2 -2
  182. package/core/built/admin/assets/posts/{sheet-CXHmD_eT.js → sheet-CNe7II5c.js} +3 -3
  183. package/core/built/admin/assets/posts/{site-C6DajagV.js → site-H9RAiQVv.js} +2 -2
  184. package/core/built/admin/assets/posts/{skeleton-Bihnkn8q.js → skeleton-Cn6x1x_C.js} +3 -3
  185. package/core/built/admin/assets/posts/{source-icon-C3oQy3UI.js → source-icon-BZOYujd1.js} +4 -4
  186. package/core/built/admin/assets/posts/{stats-DFicPtCX.js → stats-ugHCg8Oe.js} +4 -4
  187. package/core/built/admin/assets/posts/{table-DSJmeU5t.js → table-dG3Q5X95.js} +2 -2
  188. package/core/built/admin/assets/posts/{tabs-oRdVpN8P.js → tabs--EW8uL5B.js} +4 -4
  189. package/core/built/admin/assets/posts/{tags-6v5Qt_05.js → tags-DVdOfiSf.js} +2 -2
  190. package/core/built/admin/assets/posts/{tags-Upnkm0CF.js → tags-i0HJqNYU.js} +16 -16
  191. package/core/built/admin/assets/posts/{tooltip-Ci7pesWi.js → tooltip-ZvsT1Fs9.js} +4 -4
  192. package/core/built/admin/assets/posts/{trash-2-NCYWjRlg.js → trash-2-BT9fEHUL.js} +2 -2
  193. package/core/built/admin/assets/posts/{value-CcwzOh2_.js → value-DoFcsWDk.js} +2 -2
  194. package/core/built/admin/assets/posts/{virtual-list-window-CNahQBXY.js → virtual-list-window-Bt47XODn.js} +3 -3
  195. package/core/built/admin/assets/posts/{web-C3KrPq0T.js → web-BMTRLT-h.js} +20 -20
  196. package/core/built/admin/assets/posts/{zap-k30mZEuO.js → zap-DcIfEzKX.js} +2 -2
  197. package/core/built/admin/assets/{posts-DAfLuxAA.js → posts-DODOCB1E.js} +1 -1
  198. package/core/built/admin/assets/{power-Drkzg1xX.js → power-BZ7BM82m.js} +1 -1
  199. package/core/built/admin/assets/{referrers-TYxTJ2On.js → referrers-xwNb6L-8.js} +1 -1
  200. package/core/built/admin/assets/{repeat-CTM1qAzl.js → repeat-DHMCQMCA.js} +1 -1
  201. package/core/built/admin/assets/{reply-DXN16g7W.js → reply-DoLURZi2.js} +1 -1
  202. package/core/built/admin/assets/{rocket-DLixlWs0.js → rocket-BDH89dZr.js} +1 -1
  203. package/core/built/admin/assets/{select-DbiLEqYb.js → select-oE39QRll.js} +1 -1
  204. package/core/built/admin/assets/{send-B71C_46D.js → send-QgRN_yRy.js} +1 -1
  205. package/core/built/admin/assets/{settings-BBMPA3MP.js → settings-CF4NLxuS.js} +4 -4
  206. package/core/built/admin/assets/{settings-lSFHFrsJ.js → settings-D7e6KLAi.js} +1 -1
  207. package/core/built/admin/assets/{share-modal-CXrYja6o.js → share-modal-BxIh7i7o.js} +1 -1
  208. package/core/built/admin/assets/{sort-button-WYo-9Kge.js → sort-button-CEe_40Pi.js} +1 -1
  209. package/core/built/admin/assets/{source-icon-D6BzjYPP.js → source-icon-CzTNLO6j.js} +1 -1
  210. package/core/built/admin/assets/{sprout-DVfG_qhy.js → sprout-B2fN41p-.js} +1 -1
  211. package/core/built/admin/assets/{square-BbUJwQJi.js → square-CJZsjE-3.js} +1 -1
  212. package/core/built/admin/assets/{stats-CnXALQ4j.js → stats-jTxCK1CB.js} +1 -1
  213. package/core/built/admin/assets/{stats-view-B-yeMUkl.js → stats-view-B4jz69N8.js} +1 -1
  214. package/core/built/admin/assets/{step-1-DUl_hlpV.js → step-1-BovjhDNc.js} +1 -1
  215. package/core/built/admin/assets/{step-2-DjX_A4Ke.js → step-2-ClG40BfX.js} +1 -1
  216. package/core/built/admin/assets/{step-3-CnDCCA8T.js → step-3-ZMSyEHZI.js} +1 -1
  217. package/core/built/admin/assets/{table-AagTvMa9.js → table-B8Pj96or.js} +1 -1
  218. package/core/built/admin/assets/{tabs-IDJWnQD-.js → tabs-BZosOk0o.js} +1 -1
  219. package/core/built/admin/assets/{tags-CXAcIDw-.js → tags-BpWoYBfB.js} +1 -1
  220. package/core/built/admin/assets/{tags-C3lykbgu.js → tags-DZukMyuU.js} +1 -1
  221. package/core/built/admin/assets/{textarea-Cc3nUXep.js → textarea-BtnzsOk7.js} +1 -1
  222. package/core/built/admin/assets/{thumbs-down-SsjC2lMD.js → thumbs-down-CZTzyz7D.js} +1 -1
  223. package/core/built/admin/assets/{tiers-J8AeZmRd.js → tiers-Do7zBqAk.js} +1 -1
  224. package/core/built/admin/assets/{toggle-group-BzD8yimX.js → toggle-group-abk9FYXi.js} +1 -1
  225. package/core/built/admin/assets/{topic-filter-CA_L6ehz.js → topic-filter-CdMvjkmc.js} +1 -1
  226. package/core/built/admin/assets/{trash-BKhRqxsX.js → trash-DNXRsBf0.js} +1 -1
  227. package/core/built/admin/assets/{underline-_HIgX6e1.js → underline-HXWuVCAi.js} +1 -1
  228. package/core/built/admin/assets/{undo-2-ClkkOS0O.js → undo-2-C2ntNJKC.js} +1 -1
  229. package/core/built/admin/assets/{upload-DEPHbyvo.js → upload-D52l7HIb.js} +1 -1
  230. package/core/built/admin/assets/{use-growth-stats-BM2qWt0C.js → use-growth-stats-Dgs0UxDT.js} +1 -1
  231. package/core/built/admin/assets/{use-simple-pagination-zyklfekn.js → use-simple-pagination-Cj2rVggA.js} +1 -1
  232. package/core/built/admin/assets/{user-round-check-DoaGU2hC.js → user-round-check-C0GdT99c.js} +1 -1
  233. package/core/built/admin/assets/{user-round-x-CwEKNuU6.js → user-round-x-CVCvxuUG.js} +1 -1
  234. package/core/built/admin/assets/{virtual-list-window-C0Cv0gUC.js → virtual-list-window-mdQS2bt_.js} +1 -1
  235. package/core/built/admin/assets/{wallet-cards-BjdG5glh.js → wallet-cards-CtLF01jQ.js} +1 -1
  236. package/core/built/admin/assets/{web-BZNnanCT.js → web-DTib8_n8.js} +1 -1
  237. package/core/built/admin/index.html +6 -6
  238. package/core/frontend/helpers/ghost_foot.js +12 -2
  239. package/core/frontend/helpers/prev_post.js +1 -1
  240. package/core/frontend/helpers/tpl/gift-toast.hbs +130 -0
  241. package/core/frontend/services/proxy.js +9 -0
  242. package/core/frontend/services/routing/controllers/entry/canonical-url.js +15 -0
  243. package/core/frontend/services/routing/controllers/entry/canonical-url.ts +14 -0
  244. package/core/frontend/services/routing/controllers/entry/gift-links.js +82 -0
  245. package/core/frontend/services/routing/controllers/entry/gift-links.ts +92 -0
  246. package/core/frontend/services/routing/controllers/entry/markdown.js +62 -0
  247. package/core/frontend/services/routing/controllers/entry/markdown.ts +66 -0
  248. package/core/frontend/services/routing/controllers/entry.js +96 -105
  249. package/core/frontend/services/routing/controllers/entry.ts +97 -0
  250. package/core/frontend/services/routing/controllers/index.js +1 -1
  251. package/core/frontend/web/middleware/frontend-caching.js +9 -0
  252. package/core/server/adapters/cache/Redis.js +0 -1
  253. package/core/server/adapters/redirects/RedirectsStoreBase.d.ts +0 -4
  254. package/core/server/adapters/scheduling/scheduling-default.js +8 -1
  255. package/core/server/api/endpoints/gift-links.js +23 -16
  256. package/core/server/api/endpoints/gift-links.ts +25 -17
  257. package/core/server/api/endpoints/index.js +0 -1
  258. package/core/server/api/endpoints/previews.js +3 -24
  259. package/core/server/api/endpoints/utils/serializers/output/gift-links.js +4 -4
  260. package/core/server/api/endpoints/utils/serializers/output/gift-links.ts +4 -4
  261. package/core/server/api/endpoints/utils/serializers/output/index.js +0 -1
  262. package/core/server/api/endpoints/utils/validators/input/automation_email_previews.js +8 -9
  263. package/core/server/api/endpoints/utils/validators/input/index.js +0 -1
  264. package/core/server/data/migrations/versions/6.48/2026-06-23-00-00-00-drop-gift-links-revoked-at.js +6 -0
  265. package/core/server/data/migrations/versions/6.48/2026-06-23-00-00-01-rename-gift-links-revoke-all-permission-to-remove-all.js +37 -0
  266. package/core/server/data/migrations/versions/6.48/2026-06-24-00-00-00-drop-gift-links-redemption-columns.js +10 -0
  267. package/core/server/data/schema/fixtures/fixtures.json +2 -2
  268. package/core/server/data/schema/schema.js +1 -4
  269. package/core/server/data/seeders/data-generator.js +4 -1
  270. package/core/server/data/seeders/importers/members-login-events-importer.js +0 -1
  271. package/core/server/data/seeders/importers/members-newsletters-importer.js +0 -1
  272. package/core/server/data/seeders/importers/members-paid-subscription-events-importer.js +0 -1
  273. package/core/server/data/seeders/importers/members-status-events-importer.js +0 -1
  274. package/core/server/data/seeders/importers/members-stripe-customers-importer.js +0 -1
  275. package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +0 -1
  276. package/core/server/data/seeders/importers/members-subscribe-events-importer.js +0 -1
  277. package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +0 -1
  278. package/core/server/lib/lexical.js +62 -36
  279. package/core/server/lib/post-revisions.js +0 -2
  280. package/core/server/lib/post-revisions.ts +0 -3
  281. package/core/server/models/index.js +0 -3
  282. package/core/server/services/auth/reset-authentication.js +0 -2
  283. package/core/server/services/auth/reset-authentication.ts +0 -3
  284. package/core/server/services/auth/session/index.js +0 -1
  285. package/core/server/services/automations/automations-api.js +59 -17
  286. package/core/server/services/automations/automations-api.ts +69 -16
  287. package/core/server/services/automations/database-automations-repository.js +58 -3
  288. package/core/server/services/automations/database-automations-repository.ts +70 -3
  289. package/core/server/services/automations/stale-lock-cutoff.js +7 -0
  290. package/core/server/services/automations/stale-lock-cutoff.ts +5 -0
  291. package/core/server/services/email-address/email-address-parser.js.d.ts +0 -1
  292. package/core/server/services/email-service/batch-sending-service.js +99 -26
  293. package/core/server/services/email-service/email-service-wrapper.js +7 -2
  294. package/core/server/services/email-service/email-service.js +146 -4
  295. package/core/server/services/gift-links/database.js +0 -3
  296. package/core/server/services/gift-links/database.ts +1 -4
  297. package/core/server/services/gift-links/index.js +2 -2
  298. package/core/server/services/gift-links/index.ts +4 -2
  299. package/core/server/services/gift-links/models.js +0 -2
  300. package/core/server/services/gift-links/models.ts +0 -2
  301. package/core/server/services/gift-links/queries.js +2 -2
  302. package/core/server/services/gift-links/queries.ts +2 -2
  303. package/core/server/services/gift-links/serializers.js +0 -2
  304. package/core/server/services/gift-links/serializers.ts +0 -2
  305. package/core/server/services/gift-links/service.js +54 -35
  306. package/core/server/services/gift-links/service.ts +70 -35
  307. package/core/server/services/gifts/gift-reminder-scheduler.js +0 -2
  308. package/core/server/services/gifts/gift-reminder-scheduler.ts +0 -2
  309. package/core/server/services/indexnow.js +8 -1
  310. package/core/server/services/internal-keys/index.js +0 -1
  311. package/core/server/services/internal-keys/index.ts +0 -2
  312. package/core/server/services/lib/dynamic-redirect-manager.d.ts +0 -1
  313. package/core/server/services/lib/in-memory-repository.js +0 -1
  314. package/core/server/services/lib/in-memory-repository.ts +0 -2
  315. package/core/server/services/members/api.js +0 -3
  316. package/core/server/services/members/create-paid-member-shim.js +31 -0
  317. package/core/server/services/members/create-paid-member-shim.ts +31 -0
  318. package/core/server/services/members/emails/signin.js +0 -1
  319. package/core/server/services/members/members-api/members-api.js +0 -2
  320. package/core/server/services/members/members-api/repositories/member-repository.js +0 -3
  321. package/core/server/services/members/service.js +4 -0
  322. package/core/server/services/post-scheduling/index.js +0 -2
  323. package/core/server/services/post-scheduling/index.ts +0 -2
  324. package/core/server/services/post-scheduling/post-scheduling.js +0 -5
  325. package/core/server/services/post-scheduling/post-scheduling.ts +0 -5
  326. package/core/server/services/recommendations/service/bookshelf-repository.js +0 -1
  327. package/core/server/services/recommendations/service/bookshelf-repository.ts +0 -4
  328. package/core/server/services/recommendations/service/incoming-recommendation-service.js +0 -1
  329. package/core/server/services/recommendations/service/incoming-recommendation-service.ts +0 -1
  330. package/core/server/services/recommendations/service/recommendation-controller.js +0 -1
  331. package/core/server/services/recommendations/service/recommendation-controller.ts +0 -1
  332. package/core/server/services/recommendations/service/recommendation-metadata-service.js +0 -1
  333. package/core/server/services/recommendations/service/recommendation-metadata-service.ts +0 -1
  334. package/core/server/services/recommendations/service/recommendation.js +0 -1
  335. package/core/server/services/recommendations/service/recommendation.ts +0 -1
  336. package/core/server/services/remote-flags/index.js +0 -1
  337. package/core/server/services/remote-flags/index.ts +0 -1
  338. package/core/server/services/slack.js +5 -1
  339. package/core/server/services/update-check/index.js +0 -1
  340. package/core/server/services/url/lazy-find-resource.js +0 -1
  341. package/core/server/services/url/lazy-find-resource.ts +0 -2
  342. package/core/server/services/url/lazy-url-service.js +0 -1
  343. package/core/server/services/url/lazy-url-service.ts +0 -2
  344. package/core/server/services/url/permalink-matcher.js +0 -1
  345. package/core/server/services/url/permalink-matcher.ts +0 -2
  346. package/core/server/services/url/router-filter.js +0 -2
  347. package/core/server/services/url/router-filter.ts +0 -2
  348. package/core/server/services/url/url-service-facade.js +0 -1
  349. package/core/server/services/url/url-service-facade.ts +0 -2
  350. package/core/server/services/verification/verification-webhook-service.js +0 -1
  351. package/core/server/services/verification/verification-webhook-service.ts +0 -1
  352. package/core/server/web/api/endpoints/admin/routes.js +7 -7
  353. package/core/shared/events-ts/post-deleted-event.js +0 -2
  354. package/core/shared/events-ts/post-deleted-event.ts +0 -2
  355. package/core/shared/labs.js +0 -1
  356. package/package.json +8 -16
  357. package/pnpm-lock.yaml +162 -113
  358. package/core/built/admin/assets/automations-BthXBzzq.js +0 -1
  359. package/core/built/admin/assets/editor-91xrI2A3.js +0 -8
  360. package/core/built/admin/assets/index-BYb09f-7.css +0 -1
  361. package/core/built/admin/assets/minus-DmrVsJ-z.js +0 -1
  362. package/core/built/admin/assets/modals-dnqxCS3l.js +0 -35
  363. package/core/server/models/outbox.js +0 -24
  364. package/core/server/services/outbox/events/start-outbox-processing-event.js +0 -22
  365. package/core/server/services/outbox/handlers/member-created.js +0 -85
  366. package/core/server/services/outbox/index.js +0 -49
  367. package/core/server/services/outbox/jobs/index.js +0 -30
  368. package/core/server/services/outbox/jobs/lib/constants.js +0 -17
  369. package/core/server/services/outbox/jobs/lib/process-entries.js +0 -117
  370. package/core/server/services/outbox/jobs/lib/process-outbox.js +0 -96
  371. package/core/server/services/outbox/jobs/outbox-job.js +0 -36
  372. package/types/ghost-storage-base.d.ts +0 -22
@@ -0,0 +1,92 @@
1
+ import {format, parse} from 'node:url';
2
+ import type {ParsedUrlQueryInput} from 'node:querystring';
3
+ import type {Request} from 'express';
4
+ import type {Entry, EntryResponse} from '../entry';
5
+
6
+ const dataService = require('../../../data');
7
+ const renderer = require('../../../rendering');
8
+ const proxy = require('../../../proxy');
9
+ const hbs = require('../../../theme-engine/engine');
10
+
11
+ /**
12
+ * Build this request's URL with `?gift` removed, preserving path, subdirectory
13
+ * and other query params. Rebuilt from req.query (qs-parsed) so bracket forms
14
+ * like `?gift[]=x` are dropped too — otherwise the stripped redirect would loop.
15
+ */
16
+ function strippedGiftUrl(req: Request): string {
17
+ const query = {...req.query} as ParsedUrlQueryInput;
18
+ delete query.gift;
19
+ return format({pathname: parse(req.originalUrl).pathname, query});
20
+ }
21
+
22
+ /**
23
+ * Flag the render as a gift view so `ghost_foot` injects the toast. Stores the
24
+ * token (not just a boolean) for a later analytics pass-through. Internal flag,
25
+ * not a public theme API.
26
+ */
27
+ function setGiftTemplateFlag(res: EntryResponse, token: string): void {
28
+ const localTemplateOptions = hbs.getLocalTemplateOptions(res.locals);
29
+ hbs.updateLocalTemplateOptions(res.locals, {
30
+ ...localTemplateOptions,
31
+ data: {
32
+ ...localTemplateOptions.data,
33
+ _gift: token
34
+ }
35
+ });
36
+ }
37
+
38
+ /**
39
+ * Whether this request is an attempt to use a gift link: the feature is enabled
40
+ * and a `?gift` param is present (in any form).
41
+ */
42
+ export function isGiftRequest(req: Request): boolean {
43
+ return proxy.labs.isSet('giftLinks') && req.query.gift !== undefined;
44
+ }
45
+
46
+ /**
47
+ * The gift token from the request, or null. A non-string form (e.g. `?gift[]=x`)
48
+ * isn't a token.
49
+ */
50
+ function giftToken(req: Request): string | null {
51
+ return typeof req.query.gift === 'string' ? req.query.gift : null;
52
+ }
53
+
54
+ /**
55
+ * Re-read the entry as a paid-member shim (the grant `/p/` previews use) to
56
+ * reveal gated content, then render it. The shim is passed as the read context
57
+ * only, so it never leaks into res.locals/@member.
58
+ */
59
+ async function renderUnlocked(req: Request, res: EntryResponse, token: string) {
60
+ const giftLookup = await dataService.entryLookup(req.path, res.routerOptions, {
61
+ ...res.locals,
62
+ member: await proxy.createPaidMemberShim()
63
+ });
64
+
65
+ // Don't index the unlocked variant; keep the token out of the Referer on
66
+ // sub-resource requests.
67
+ res.set('X-Robots-Tag', 'noindex');
68
+ res.set('Referrer-Policy', 'no-referrer');
69
+ setGiftTemplateFlag(res, token);
70
+
71
+ return renderer.renderEntry(req, res)(giftLookup.entry);
72
+ }
73
+
74
+ /**
75
+ * CASE: gift-link reader access. A gift link is the post's real URL plus
76
+ * `?gift=TOKEN`; the token is verified against the entry living at this URL, so
77
+ * a token for one post can never unlock another. A valid token unlocks the
78
+ * entry; anything else strips `?gift` and 301s to the clean URL.
79
+ */
80
+ export async function serveGiftRequest(req: Request, res: EntryResponse, entry: Entry) {
81
+ const token = giftToken(req);
82
+
83
+ if (token && await proxy.giftLinks.service.isValidTokenForPost(token, entry.id)) {
84
+ return renderUnlocked(req, res, token);
85
+ }
86
+
87
+ // Invalid token, or one for a different post: strip it and 301 to the clean
88
+ // URL (the page still renders, paywalled). res.redirect, not
89
+ // urlUtils.redirect301, so the no-store set for ?gift requests survives and
90
+ // the token-bearing redirect isn't cached.
91
+ return res.redirect(301, strippedGiftUrl(req));
92
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isMdRequest = isMdRequest;
7
+ exports.serveMdRequest = serveMdRequest;
8
+ exports.isAcceptsRequest = isAcceptsRequest;
9
+ exports.serveAcceptsRequest = serveAcceptsRequest;
10
+ const canonical_url_1 = __importDefault(require("./canonical-url"));
11
+ const config = require('../../../../../shared/config');
12
+ const urlUtils = require('../../../../../shared/url-utils');
13
+ const { getAcceptedMarkdownContentType, getMarkdownPath, renderEntryMarkdown } = require('../../../llms/markdown');
14
+ const MEMBERS_ONLY_MARKDOWN = '# Members-only content\n\nThis post requires a subscription and is not available for public access.\n';
15
+ function llmsEnabled(req) {
16
+ const llmsService = req.app.get('llmsService') || null;
17
+ return Boolean(llmsService && llmsService.isEnabled());
18
+ }
19
+ function isPublic(entry) {
20
+ return entry.visibility === 'public';
21
+ }
22
+ function serveMarkdown(res, entry) {
23
+ const llmsIndexUrl = urlUtils.urlFor({ relativeUrl: '/llms.txt' }, true);
24
+ res.set('Cache-Control', `public, max-age=${config.get('caching:llms:maxAge')}`);
25
+ res.set('Content-Location', getMarkdownPath(new URL(entry.url).pathname));
26
+ res.type('text/markdown');
27
+ return res.send(renderEntryMarkdown(entry, { llmsIndexUrl }));
28
+ }
29
+ /**
30
+ * Whether this is a `.md` URL request (the scoped suffix route sets the flag).
31
+ */
32
+ function isMdRequest(res) {
33
+ return Boolean(res.routerOptions.isMarkdownRequest);
34
+ }
35
+ /**
36
+ * Serve a `.md` URL as markdown for LLM consumption. When the feature is
37
+ * disabled we redirect to the canonical (html) url; members-only content is
38
+ * refused.
39
+ */
40
+ function serveMdRequest(req, res, entry) {
41
+ if (!llmsEnabled(req)) {
42
+ return res.redirect(302, (0, canonical_url_1.default)(req, entry));
43
+ }
44
+ if (!isPublic(entry)) {
45
+ return res.status(403).type('text/markdown').send(MEMBERS_ONLY_MARKDOWN);
46
+ }
47
+ return serveMarkdown(res, entry);
48
+ }
49
+ /**
50
+ * Whether the request negotiates markdown via the Accept header — only for a
51
+ * public entry with the llms feature enabled, otherwise it renders as html.
52
+ */
53
+ function isAcceptsRequest(req, entry) {
54
+ return isPublic(entry) && Boolean(getAcceptedMarkdownContentType(req)) && llmsEnabled(req);
55
+ }
56
+ /**
57
+ * Serve markdown negotiated via the Accept header.
58
+ */
59
+ function serveAcceptsRequest(res, entry) {
60
+ res.vary('Accept');
61
+ return serveMarkdown(res, entry);
62
+ }
@@ -0,0 +1,66 @@
1
+ import type {Request, Response} from 'express';
2
+ import type {Entry, EntryResponse} from '../entry';
3
+ import buildCanonicalUrl from './canonical-url';
4
+
5
+ const config = require('../../../../../shared/config');
6
+ const urlUtils = require('../../../../../shared/url-utils');
7
+ const {getAcceptedMarkdownContentType, getMarkdownPath, renderEntryMarkdown} = require('../../../llms/markdown');
8
+
9
+ const MEMBERS_ONLY_MARKDOWN = '# Members-only content\n\nThis post requires a subscription and is not available for public access.\n';
10
+
11
+ function llmsEnabled(req: Request): boolean {
12
+ const llmsService = req.app.get('llmsService') || null;
13
+ return Boolean(llmsService && llmsService.isEnabled());
14
+ }
15
+
16
+ function isPublic(entry: Entry): boolean {
17
+ return entry.visibility === 'public';
18
+ }
19
+
20
+ function serveMarkdown(res: Response, entry: Entry) {
21
+ const llmsIndexUrl = urlUtils.urlFor({relativeUrl: '/llms.txt'}, true);
22
+ res.set('Cache-Control', `public, max-age=${config.get('caching:llms:maxAge')}`);
23
+ res.set('Content-Location', getMarkdownPath(new URL(entry.url).pathname));
24
+ res.type('text/markdown');
25
+ return res.send(renderEntryMarkdown(entry, {llmsIndexUrl}));
26
+ }
27
+
28
+ /**
29
+ * Whether this is a `.md` URL request (the scoped suffix route sets the flag).
30
+ */
31
+ export function isMdRequest(res: EntryResponse): boolean {
32
+ return Boolean(res.routerOptions.isMarkdownRequest);
33
+ }
34
+
35
+ /**
36
+ * Serve a `.md` URL as markdown for LLM consumption. When the feature is
37
+ * disabled we redirect to the canonical (html) url; members-only content is
38
+ * refused.
39
+ */
40
+ export function serveMdRequest(req: Request, res: Response, entry: Entry) {
41
+ if (!llmsEnabled(req)) {
42
+ return res.redirect(302, buildCanonicalUrl(req, entry));
43
+ }
44
+
45
+ if (!isPublic(entry)) {
46
+ return res.status(403).type('text/markdown').send(MEMBERS_ONLY_MARKDOWN);
47
+ }
48
+
49
+ return serveMarkdown(res, entry);
50
+ }
51
+
52
+ /**
53
+ * Whether the request negotiates markdown via the Accept header — only for a
54
+ * public entry with the llms feature enabled, otherwise it renders as html.
55
+ */
56
+ export function isAcceptsRequest(req: Request, entry: Entry): boolean {
57
+ return isPublic(entry) && Boolean(getAcceptedMarkdownContentType(req)) && llmsEnabled(req);
58
+ }
59
+
60
+ /**
61
+ * Serve markdown negotiated via the Accept header.
62
+ */
63
+ export function serveAcceptsRequest(res: Response, entry: Entry) {
64
+ res.vary('Accept');
65
+ return serveMarkdown(res, entry);
66
+ }
@@ -1,114 +1,105 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.entryController = entryController;
40
+ const markdown = __importStar(require("./entry/markdown"));
41
+ const giftLinks = __importStar(require("./entry/gift-links"));
42
+ const canonical_url_1 = __importDefault(require("./entry/canonical-url"));
1
43
  const debug = require('@tryghost/debug')('services:routing:controllers:entry');
2
- const url = require('url');
3
44
  const config = require('../../../../shared/config');
4
45
  const urlUtils = require('../../../../shared/url-utils');
5
46
  const dataService = require('../../data');
6
47
  const renderer = require('../../rendering');
7
- const {getAcceptedMarkdownContentType, getMarkdownPath, renderEntryMarkdown} = require('../../llms/markdown');
8
-
9
- function getLlmsService(req) {
10
- return req.app.get('llmsService') || null;
11
- }
12
-
13
- function serveMarkdown(res, entry) {
14
- const llmsIndexUrl = urlUtils.urlFor({relativeUrl: '/llms.txt'}, true);
15
- res.set('Cache-Control', `public, max-age=${config.get('caching:llms:maxAge')}`);
16
- res.set('Content-Location', getMarkdownPath(new URL(entry.url).pathname));
17
- res.type('text/markdown');
18
- return res.send(renderEntryMarkdown(entry, {llmsIndexUrl}));
48
+ /**
49
+ * The request's last url param is `/edit`: redirect to the admin editor, or fall
50
+ * through to a 404 when admin redirects are disabled.
51
+ */
52
+ function editRedirect(res, next, entry) {
53
+ if (!config.get('admin:redirects')) {
54
+ debug('is edit url but admin redirects are disabled');
55
+ return next();
56
+ }
57
+ debug('redirect. is edit url');
58
+ const resourceType = res.routerOptions.context?.includes('page') ? 'page' : 'post';
59
+ return urlUtils.redirectToAdmin(302, res, `/#/editor/${resourceType}/${entry.id}`);
19
60
  }
20
-
21
61
  /**
22
- * @description Entry controller.
23
- * @param {Object} req
24
- * @param {Object} res
25
- * @param {Function} next
26
- * @returns {Promise}
62
+ * The requested path no longer matches the entry's canonical url — happens with
63
+ * date permalinks after a publish date change.
27
64
  */
28
- module.exports = function entryController(req, res, next) {
65
+ function isPermalinkStale(req, entry) {
66
+ return urlUtils.absoluteToRelative(entry.url, { withoutSubdirectory: true }) !== req.path;
67
+ }
68
+ async function entryController(req, res, next) {
29
69
  debug('entryController', res.routerOptions);
30
-
31
- return dataService.entryLookup(req.path, res.routerOptions, res.locals)
32
- .then(function then(lookup) {
33
- // Format data 1
34
- const entry = lookup ? lookup.entry : false;
35
-
36
- if (!entry) {
37
- debug('no entry');
38
- return next();
39
- }
40
-
41
- // CASE: postlookup can detect options for example /edit, unknown options get ignored and end in 404
42
- if (lookup.isUnknownOption) {
43
- debug('isUnknownOption');
44
- return next();
45
- }
46
-
47
- // CASE: last param is of url is /edit, redirect to admin
48
- if (lookup.isEditURL) {
49
- if (!config.get('admin:redirects')) {
50
- debug('is edit url but admin redirects are disabled');
51
- return next();
52
- }
53
-
54
- debug('redirect. is edit url');
55
- const resourceType = res.routerOptions?.context?.includes('page') ? 'page' : 'post';
56
-
57
- return urlUtils.redirectToAdmin(302, res, `/#/editor/${resourceType}/${entry.id}`);
58
- }
59
-
60
- // CASE: .md URL — serve entry as markdown for LLM consumption
61
- if (res.routerOptions.isMarkdownRequest) {
62
- const llmsService = getLlmsService(req);
63
- if (!llmsService || !llmsService.isEnabled()) {
64
- return res.redirect(302, url.format({
65
- pathname: url.parse(entry.url).pathname,
66
- search: url.parse(req.originalUrl).search
67
- }));
68
- }
69
-
70
- if (entry.visibility !== 'public') {
71
- return res.status(403).type('text/markdown').send(
72
- '# Members-only content\n\nThis post requires a subscription and is not available for public access.\n'
73
- );
74
- }
75
-
76
- return serveMarkdown(res, entry);
77
- }
78
-
79
- /**
80
- * CASE: Permalink is not valid anymore, we redirect him permanently to the correct one
81
- * This should only happen if you have date permalinks enabled and you change
82
- * your publish date.
83
- *
84
- * @NOTE:
85
- *
86
- * Ensure we redirect to the correct post url including subdirectory.
87
- */
88
- if (urlUtils.absoluteToRelative(entry.url, {withoutSubdirectory: true}) !== req.path) {
89
- debug('redirect');
90
-
91
- return urlUtils.redirect301(res, url.format({
92
- pathname: url.parse(entry.url).pathname,
93
- search: url.parse(req.originalUrl).search
94
- }));
95
- }
96
-
97
- // CASE: Accept: text/markdown content negotiation
98
- if (entry.visibility === 'public') {
99
- const markdownContentType = getAcceptedMarkdownContentType(req);
100
-
101
- if (markdownContentType) {
102
- const llmsService = getLlmsService(req);
103
-
104
- if (llmsService && llmsService.isEnabled()) {
105
- res.vary('Accept');
106
- return serveMarkdown(res, entry);
107
- }
108
- }
109
- }
110
-
111
- return renderer.renderEntry(req, res)(entry);
112
- })
113
- .catch(renderer.handleError(next));
114
- };
70
+ try {
71
+ const lookup = await dataService.entryLookup(req.path, res.routerOptions, res.locals);
72
+ const entry = lookup ? lookup.entry : false;
73
+ if (!entry || lookup.isUnknownOption) {
74
+ debug('no entry or unknown option');
75
+ return next();
76
+ }
77
+ if (lookup.isEditURL) {
78
+ return editRedirect(res, next, entry);
79
+ }
80
+ // MUST run before the permalink redirect below: a `.md` path can never
81
+ // equal the entry's canonical (html) path, so the redirect would always
82
+ // fire and 301 the request to html, losing the markdown intent.
83
+ if (markdown.isMdRequest(res)) {
84
+ return markdown.serveMdRequest(req, res, entry);
85
+ }
86
+ if (isPermalinkStale(req, entry)) {
87
+ debug('redirect');
88
+ return urlUtils.redirect301(res, (0, canonical_url_1.default)(req, entry));
89
+ }
90
+ // MUST run after the permalink redirect above: negotiation rides on the
91
+ // canonical URL, so a stale dated-permalink URL is 301'd to canonical
92
+ // first, then markdown is served.
93
+ if (markdown.isAcceptsRequest(req, entry)) {
94
+ return markdown.serveAcceptsRequest(res, entry);
95
+ }
96
+ if (giftLinks.isGiftRequest(req)) {
97
+ return await giftLinks.serveGiftRequest(req, res, entry);
98
+ }
99
+ return renderer.renderEntry(req, res)(entry);
100
+ }
101
+ catch (err) {
102
+ return renderer.handleError(next)(err);
103
+ }
104
+ }
105
+ ;
@@ -0,0 +1,97 @@
1
+ import type {NextFunction, Request, Response} from 'express';
2
+ import * as markdown from './entry/markdown';
3
+ import * as giftLinks from './entry/gift-links';
4
+ import buildCanonicalUrl from './entry/canonical-url';
5
+
6
+ const debug = require('@tryghost/debug')('services:routing:controllers:entry');
7
+ const config = require('../../../../shared/config');
8
+ const urlUtils = require('../../../../shared/url-utils');
9
+ const dataService = require('../../data');
10
+ const renderer = require('../../rendering');
11
+
12
+ // Routing context the router attaches to the matched entry's response.
13
+ export interface RouterOptions {
14
+ isMarkdownRequest?: boolean;
15
+ context?: string[];
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ export interface EntryResponse extends Response {
20
+ routerOptions: RouterOptions;
21
+ }
22
+
23
+ // The resolved post/page entry from the data layer; only the fields used here are typed.
24
+ export interface Entry {
25
+ id: string;
26
+ url: string;
27
+ visibility: string;
28
+ [key: string]: unknown;
29
+ }
30
+
31
+ /**
32
+ * The request's last url param is `/edit`: redirect to the admin editor, or fall
33
+ * through to a 404 when admin redirects are disabled.
34
+ */
35
+ function editRedirect(res: EntryResponse, next: NextFunction, entry: Entry) {
36
+ if (!config.get('admin:redirects')) {
37
+ debug('is edit url but admin redirects are disabled');
38
+ return next();
39
+ }
40
+
41
+ debug('redirect. is edit url');
42
+ const resourceType = res.routerOptions.context?.includes('page') ? 'page' : 'post';
43
+ return urlUtils.redirectToAdmin(302, res, `/#/editor/${resourceType}/${entry.id}`);
44
+ }
45
+
46
+ /**
47
+ * The requested path no longer matches the entry's canonical url — happens with
48
+ * date permalinks after a publish date change.
49
+ */
50
+ function isPermalinkStale(req: Request, entry: Entry): boolean {
51
+ return urlUtils.absoluteToRelative(entry.url, {withoutSubdirectory: true}) !== req.path;
52
+ }
53
+
54
+ export async function entryController(req: Request, res: EntryResponse, next: NextFunction): Promise<void | Response> {
55
+ debug('entryController', res.routerOptions);
56
+
57
+ try {
58
+ const lookup = await dataService.entryLookup(req.path, res.routerOptions, res.locals);
59
+ const entry = lookup ? lookup.entry : false;
60
+
61
+ if (!entry || lookup.isUnknownOption) {
62
+ debug('no entry or unknown option');
63
+ return next();
64
+ }
65
+
66
+ if (lookup.isEditURL) {
67
+ return editRedirect(res, next, entry);
68
+ }
69
+
70
+ // MUST run before the permalink redirect below: a `.md` path can never
71
+ // equal the entry's canonical (html) path, so the redirect would always
72
+ // fire and 301 the request to html, losing the markdown intent.
73
+ if (markdown.isMdRequest(res)) {
74
+ return markdown.serveMdRequest(req, res, entry);
75
+ }
76
+
77
+ if (isPermalinkStale(req, entry)) {
78
+ debug('redirect');
79
+ return urlUtils.redirect301(res, buildCanonicalUrl(req, entry));
80
+ }
81
+
82
+ // MUST run after the permalink redirect above: negotiation rides on the
83
+ // canonical URL, so a stale dated-permalink URL is 301'd to canonical
84
+ // first, then markdown is served.
85
+ if (markdown.isAcceptsRequest(req, entry)) {
86
+ return markdown.serveAcceptsRequest(res, entry);
87
+ }
88
+
89
+ if (giftLinks.isGiftRequest(req)) {
90
+ return await giftLinks.serveGiftRequest(req, res, entry);
91
+ }
92
+
93
+ return renderer.renderEntry(req, res)(entry);
94
+ } catch (err) {
95
+ return renderer.handleError(next)(err);
96
+ }
97
+ };
@@ -1,6 +1,6 @@
1
1
  module.exports = {
2
2
  get entry() {
3
- return require('./entry');
3
+ return require('./entry').entryController;
4
4
  },
5
5
 
6
6
  get collection() {
@@ -2,6 +2,7 @@
2
2
  * @file Middleware to set the appropriate cache headers on the frontend
3
3
  */
4
4
  const config = require('../../../shared/config');
5
+ const labs = require('../../../shared/labs');
5
6
  const shared = require('../../../server/web/shared');
6
7
  const {api} = require('../../services/proxy');
7
8
  const preview = require('../../services/theme-engine/preview');
@@ -69,6 +70,14 @@ const getMiddleware = async (getFreeTier = async () => {
69
70
  return shared.middleware.cacheControl('noCache')(req, res, next);
70
71
  }
71
72
 
73
+ // CASE: never cache gift-link reads. A ?gift render holds unlocked gated
74
+ // content with no member cookie, so the edge would otherwise serve it to
75
+ // everyone. Keyed on the param's presence so an empty `?gift=` still
76
+ // bypasses.
77
+ if (req.query?.gift !== undefined && labs.isSet('giftLinks')) {
78
+ return shared.middleware.cacheControl('noCache')(req, res, next);
79
+ }
80
+
72
81
  // CASE: Cache member's content if this feature is enabled
73
82
  if (req.member && shouldCacheMembersContent) {
74
83
  // Set the 'cache-control' header to 'public'
@@ -5,7 +5,6 @@
5
5
  * @see core/shared/config/defaults.json
6
6
  * @see core/server/services/adapter-manager/adapter-manager.js
7
7
  */
8
- /* eslint-disable ghost/filenames/match-regex */
9
8
  const RedisCache = require('../lib/redis/AdapterCacheRedis');
10
9
 
11
10
  module.exports = RedisCache;
@@ -1,7 +1,3 @@
1
- /* eslint-disable ghost/filenames/match-regex --
2
- * PascalCase mirrors the runtime `RedirectsStoreBase.js` so the TS
3
- * adapter can default-import via the matching declaration file.
4
- */
5
1
  declare class RedirectsStoreBase {
6
2
  readonly requiredFns: ReadonlyArray<string>;
7
3
  }
@@ -36,6 +36,13 @@ function SchedulingDefault(options) {
36
36
 
37
37
  this.deletedJobs = {};
38
38
  this.isRunning = false;
39
+
40
+ // The HTTP client is held as an instance property so unit tests can
41
+ // swap it for a deterministic stub. Doing the dependency injection in
42
+ // the constructor (rather than `require`ing inside _pingUrl) avoids
43
+ // pulling `@tryghost/request`'s cacheable-lookup singleton — racy
44
+ // under nock + shared workers — into the unit-test path.
45
+ this.request = request;
39
46
  }
40
47
 
41
48
  util.inherits(SchedulingDefault, SchedulingBase);
@@ -292,7 +299,7 @@ SchedulingDefault.prototype._pingUrl = function (object) {
292
299
  }
293
300
  }
294
301
 
295
- return request(url, options).catch((err) => {
302
+ return this.request(url, options).catch((err) => {
296
303
  const {statusCode} = err;
297
304
 
298
305
  // CASE: post/page was deleted already