ghost 6.54.0 → 6.55.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 (327) 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.2.0.tgz +0 -0
  4. package/components/tryghost-adapter-base-sso-0.1.1.tgz +0 -0
  5. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  6. package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
  7. package/core/boot.js +3 -6
  8. package/core/built/admin/assets/{account-migration-DLvVA5yq.js → account-migration-sjHLx5dy.js} +1 -1
  9. package/core/built/admin/assets/activitypub/{account-migration-DHbZcxGM.js → account-migration-DoYXtnZ8.js} +4 -4
  10. package/core/built/admin/assets/activitypub/activitypub.js +2 -2
  11. package/core/built/admin/assets/activitypub/{bluesky-sharing-zfxUey_L.js → bluesky-sharing-Bxeh6MKW.js} +4 -4
  12. package/core/built/admin/assets/activitypub/{dist-BqkDUQxJ2.js → dist-ByFmDSNP2.js} +64 -63
  13. package/core/built/admin/assets/activitypub/{dist-Ba_QxHHl.js → dist-D8sFfdpC.js} +2 -2
  14. package/core/built/admin/assets/activitypub/{domain-CmwSZLHK.js → domain-CWueFW6q.js} +5 -5
  15. package/core/built/admin/assets/activitypub/{edit-profile-CXdwNmVG.js → edit-profile-BGxPU7N5.js} +3 -3
  16. package/core/built/admin/assets/activitypub/{explore-DfbjzGM1.js → explore-DBK-u0Ch.js} +3 -3
  17. package/core/built/admin/assets/activitypub/{feed-DSEBJOqf.js → feed-Bcir5RQZ.js} +3 -3
  18. package/core/built/admin/assets/activitypub/{field-CY7XVcx6.js → field-BV-CIT3d.js} +2 -2
  19. package/core/built/admin/assets/activitypub/{inbox-DtQ0kytT.js → inbox-yC0nfvzl.js} +4 -4
  20. package/core/built/admin/assets/activitypub/{label-Dw96GQ7u.js → label-DTRS9TDk.js} +2 -2
  21. package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +1 -0
  22. package/core/built/admin/assets/activitypub/{moderation-Dg_-I-8C.js → moderation-Dwl9t0pb.js} +4 -4
  23. package/core/built/admin/assets/activitypub/{note-Cy1-uKMY.js → note-B-zKsZ7r.js} +3 -3
  24. package/core/built/admin/assets/activitypub/{notifications-B_fvoLIM.js → notifications-DjJZD54t.js} +3 -3
  25. package/core/built/admin/assets/activitypub/{preferences-ByJseZp6.js → preferences-CoU8vs6d.js} +6 -6
  26. package/core/built/admin/assets/activitypub/{profile-BFtbnIjz.js → profile-BAsaJekx.js} +6 -6
  27. package/core/built/admin/assets/activitypub/{routes-Dh3xXfce.js → routes-CLH3OVTP.js} +28 -25
  28. package/core/built/admin/assets/activitypub/{settings-DTgqJPxM.js → settings-DA6pNJ-Q.js} +3 -3
  29. package/core/built/admin/assets/activitypub/{site-CdL9ZLqm.js → site-CQD42Pw5.js} +2 -2
  30. package/core/built/admin/assets/activitypub/{tabs-ogqwoBlb.js → tabs-Cg0saow1.js} +7 -7
  31. package/core/built/admin/assets/activitypub/{use-reply-chain-data-CxonteFD.js → use-reply-chain-data-Br4aOWOh.js} +2 -2
  32. package/core/built/admin/assets/audience-DjEWNhp3.js +1 -0
  33. package/core/built/admin/assets/{automated-emails-Cn87L5Md.js → automated-emails-DRy7qY-J.js} +2 -2
  34. package/core/built/admin/assets/{automations-BP_aHamJ.js → automations-Bx3VcFIL.js} +1 -1
  35. package/core/built/admin/assets/{automations-B7-y3dXt.js → automations-DhKeS_U5.js} +1 -1
  36. package/core/built/admin/assets/{bluesky-sharing-Bh36XPh4.js → bluesky-sharing-ClMd0-Ft.js} +1 -1
  37. package/core/built/admin/assets/{breadcrumb-D34YWnaw.js → breadcrumb-BD0U2ygg.js} +1 -1
  38. package/core/built/admin/assets/{chart-helpers-DbklaJw9.js → chart-helpers-fjQKRVyY.js} +1 -1
  39. package/core/built/admin/assets/checkbox-CWa28lgO.js +1 -0
  40. package/core/built/admin/assets/{chunk.962.b73442b07be930bb5d85.js → chunk.211.6f7328d63c41536451b3.js} +737 -559
  41. package/core/built/admin/assets/{chunk.962.b73442b07be930bb5d85.js.LICENSE.txt → chunk.211.6f7328d63c41536451b3.js.LICENSE.txt} +2 -0
  42. package/core/built/admin/assets/{chunk.589.096f4d0bb775e5229aa9.js → chunk.481.6c7395a49484bb4583aa.js} +41 -38
  43. package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +25 -0
  44. package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +27 -0
  45. package/core/built/admin/assets/code-editor-view-CCr0rBWS.js +1 -0
  46. package/core/built/admin/assets/comments-DyUSMUdj.js +1 -0
  47. package/core/built/admin/assets/{date-range-select-B5vSsrpx.js → date-range-select-BFp58EMk.js} +1 -1
  48. package/core/built/admin/assets/{disabled-sources-indicator-NkOIoMue.js → disabled-sources-indicator-CUxMfiPa.js} +1 -1
  49. package/core/built/admin/assets/dist-2Tnz2Yfq.js +1 -0
  50. package/core/built/admin/assets/dist-CjY-f5Z1.js +1 -0
  51. package/core/built/admin/assets/dist-DIC8yz5C.js +1 -0
  52. package/core/built/admin/assets/domain-C0bV2UV8.js +1 -0
  53. package/core/built/admin/assets/dropzone-DlK-K2DQ.js +5 -0
  54. package/core/built/admin/assets/{edit-profile-rvDGrS8J.js → edit-profile-BKT-REJw.js} +1 -1
  55. package/core/built/admin/assets/editor-Dp865O-4.js +8 -0
  56. package/core/built/admin/assets/{explore-BZAZOg5X.js → explore-ClUpJZOp.js} +1 -1
  57. package/core/built/admin/assets/{feed-CFV8xDw9.js → feed-DF92dUMP.js} +1 -1
  58. package/core/built/admin/assets/field-Do7lieBA.js +1 -0
  59. package/core/built/admin/assets/{filter-sources-Fx8wiYYw.js → filter-sources-BV2rnpfA.js} +1 -1
  60. package/core/built/admin/assets/filters-CUXJ2Nm9.js +1 -0
  61. package/core/built/admin/assets/{filters-CGX_pf2N.js → filters-DfbIWc_l.js} +1 -1
  62. package/core/built/admin/assets/{ghost-3063eae29e6c9cc6d6e7131928a27da0.css → ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css} +1 -1
  63. package/core/built/admin/assets/{ghost-b1dce91ad3232cc259ee5fbbf9526243.js → ghost-c4057474943effc568c5659ccc4909d7.js} +407 -406
  64. package/core/built/admin/assets/{ghost-dark-c8d0ab7f515b2010c173e23e9b32ea8a.css → ghost-dark-db36d795d21c61426837df40af270966.css} +1 -1
  65. package/core/built/admin/assets/gift-link-modal-f9VIMNDv.js +1 -0
  66. package/core/built/admin/assets/{gift-links-nBxJjZiQ.js → gift-links-CsUhefWG.js} +1 -1
  67. package/core/built/admin/assets/growth-B3I6gNLm.js +1 -0
  68. package/core/built/admin/assets/{Growth-CSaHoSVD.js → growth-CVw1My4S.js} +2 -2
  69. package/core/built/admin/assets/{hooks-BApwwBE0.js → hooks-CixO01lL.js} +1 -1
  70. package/core/built/admin/assets/{inbox-BRp1_aaC.js → inbox-CFBSu4Ed.js} +12 -12
  71. package/core/built/admin/assets/index-9Cw8z3rA.css +1 -0
  72. package/core/built/admin/assets/index-BGG7NckO.js +67 -0
  73. package/core/built/admin/assets/input-group-BSgG_lVw.js +1 -0
  74. package/core/built/admin/assets/input-surface-BpAjZXY5.js +1 -0
  75. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +974 -937
  76. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +28 -28
  77. package/core/built/admin/assets/koenig-lexical/style.css +1 -1
  78. package/core/built/admin/assets/{koenig-lexical-CM7UFNdL.js → koenig-lexical-f8vHRYvz.js} +28 -28
  79. package/core/built/admin/assets/{kpi-card-BrGH6FsT.js → kpi-card-DSap9rAy.js} +1 -1
  80. package/core/built/admin/assets/{kpi-card-CIGyijjx.js → kpi-card-DU0rn9Cj.js} +1 -1
  81. package/core/built/admin/assets/{kpi-helpers-_HRRsklE.js → kpi-helpers-Dz33AV6h.js} +1 -1
  82. package/core/built/admin/assets/{kpi-tabs-BSvh9eoq.js → kpi-tabs-BtTTrKvJ.js} +1 -1
  83. package/core/built/admin/assets/{label-BOR9XdKl.js → label-DS8Mi6G5.js} +1 -1
  84. package/core/built/admin/assets/label-picker-1DvSuLA1.js +1 -0
  85. package/core/built/admin/assets/labels-CDSUt4J_.js +1 -0
  86. package/core/built/admin/assets/layout-yQEi2qDM.js +1 -0
  87. package/core/built/admin/assets/{link-helpers-tOlxmuH5.js → link-helpers-7EYqvvHs.js} +1 -1
  88. package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +1 -0
  89. package/core/built/admin/assets/member-detail-BAq7gDaa.js +1 -0
  90. package/core/built/admin/assets/members-CQpVFJu4.js +10 -0
  91. package/core/built/admin/assets/modals-ReORvWnu.js +31 -0
  92. package/core/built/admin/assets/moderation-DySbgyUS.js +1 -0
  93. package/core/built/admin/assets/newsletter-CJQvtHqN.js +1 -0
  94. package/core/built/admin/assets/{Newsletters-C5cs6FGo.js → newsletters-B3QQXfGT.js} +1 -1
  95. package/core/built/admin/assets/{newsletters-D1jbtV-h.js → newsletters-BXJbJgA-.js} +1 -1
  96. package/core/built/admin/assets/note-C8kxrz6w.js +1 -0
  97. package/core/built/admin/assets/notifications-CgtSfX7t.js +1 -0
  98. package/core/built/admin/assets/{offers-CrY6nSRR.js → offers-DEniVRD5.js} +1 -1
  99. package/core/built/admin/assets/onboarding-route-4qtziA9d.js +1 -0
  100. package/core/built/admin/assets/overview-Bf2sbznq.js +1 -0
  101. package/core/built/admin/assets/overview-CBfePxcr.js +1 -0
  102. package/core/built/admin/assets/{page-header-C0jVxtS8.js → page-header-B3Wb9Jqv.js} +1 -1
  103. package/core/built/admin/assets/{pages-BbNWFcGj.js → pages-B4goRPUn.js} +1 -1
  104. package/core/built/admin/assets/{post-analytics-Iw7dk1mT.js → post-analytics-We-ko5Cb.js} +1 -1
  105. package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +1 -0
  106. package/core/built/admin/assets/post-analytics-provider-B6UynTGb.js +1 -0
  107. package/core/built/admin/assets/{post-share-modal-DBfKl2GZ.js → post-share-modal-DFfO93_0.js} +1 -1
  108. package/core/built/admin/assets/{posts-EWpZIjkr.js → posts-DhXE7b6r.js} +1 -1
  109. package/core/built/admin/assets/{preferences-D4EANGoc.js → preferences-DJPfHEGu.js} +2 -2
  110. package/core/built/admin/assets/profile-DqBQp-gT.js +1 -0
  111. package/core/built/admin/assets/{referrers-BCfFYSHc.js → referrers-pmJT4k3-.js} +1 -1
  112. package/core/built/admin/assets/select-C5_xEt5b.js +1 -0
  113. package/core/built/admin/assets/settings-BwiVIXau.js +4 -0
  114. package/core/built/admin/assets/{settings-Bz3RZRmE.js → settings-WXNeWyU1.js} +1 -1
  115. package/core/built/admin/assets/shade-app-B3heOAfz-CGXQwWeO.js +3 -0
  116. package/core/built/admin/assets/{share-modal-CkdTTMJd.js → share-modal-DsgW3wbg.js} +1 -1
  117. package/core/built/admin/assets/social-urls-BQPFEk1_.js +8 -0
  118. package/core/built/admin/assets/{sort-button-DZp9Y2r1.js → sort-button-Bb_FCl7e.js} +1 -1
  119. package/core/built/admin/assets/{source-icon-CHr9qJ2Y.js → source-icon-BeaRxUn9.js} +1 -1
  120. package/core/built/admin/assets/{stats-DGoXZxWE.js → stats-Bjj_kmJ1.js} +1 -1
  121. package/core/built/admin/assets/{stats-view-D2-1L0ms.js → stats-view-BxPbW6K1.js} +1 -1
  122. package/core/built/admin/assets/{step-1-RdahbwyG.js → step-1-B1e3-1F8.js} +1 -1
  123. package/core/built/admin/assets/{step-2-D83PeRwd.js → step-2-C5WaeBJo.js} +1 -1
  124. package/core/built/admin/assets/{step-3-p_Op4wv4.js → step-3-B46iVPwu.js} +2 -2
  125. package/core/built/admin/assets/switch-z5Y6Uzt6.js +1 -0
  126. package/core/built/admin/assets/{table-B8LgTQJZ.js → table-CB-4_HBJ.js} +1 -1
  127. package/core/built/admin/assets/tabs-CCEgKYzq.js +1 -0
  128. package/core/built/admin/assets/{tags-B4l891z2.js → tags-Cg2VJGmG.js} +1 -1
  129. package/core/built/admin/assets/textarea-Bjccvh_E.js +1 -0
  130. package/core/built/admin/assets/{tiers-BgYLWRJv.js → tiers-DYsa4DCo.js} +1 -1
  131. package/core/built/admin/assets/{toggle-group-DNXDkAwf.js → toggle-group-b3IlxvhV.js} +1 -1
  132. package/core/built/admin/assets/{topic-filter-DZGjEcz3.js → topic-filter-DAFT87tQ.js} +1 -1
  133. package/core/built/admin/assets/{use-analytics-data-CaT8RirX.js → use-analytics-data-S0FTd43s.js} +1 -1
  134. package/core/built/admin/assets/use-filter-params-Gpoq2E1L.js +1 -0
  135. package/core/built/admin/assets/{use-growth-stats-9Db2CuJe.js → use-growth-stats-CIaiHOVb.js} +1 -1
  136. package/core/built/admin/assets/{use-post-referrers-BV54YcQS.js → use-post-referrers-Cq2vBPrA.js} +1 -1
  137. package/core/built/admin/assets/{use-reply-chain-data-DuYZGDan.js → use-reply-chain-data-DBDRtGu-.js} +1 -1
  138. package/core/built/admin/assets/{use-simple-pagination-8fTua9Nt.js → use-simple-pagination-CjYNxdwe.js} +1 -1
  139. package/core/built/admin/assets/{use-tinybird-query-Dz156HvZ.js → use-tinybird-query-falHAtuK.js} +1 -1
  140. package/core/built/admin/assets/{use-tinybird-token-C9RUdL_n.js → use-tinybird-token-D7-8ND1y.js} +1 -1
  141. package/core/built/admin/assets/{users-CcSCQFcQ.js → users-Dkv8NQZe.js} +1 -1
  142. package/core/built/admin/assets/{virtual-list-BCvP0YF-.js → virtual-list-CvnC9zF2.js} +1 -1
  143. package/core/built/admin/assets/web-B_5GZRvH.js +1 -0
  144. package/core/built/admin/assets/web-jFYWCKDn.js +2 -0
  145. package/core/built/admin/index.html +14 -10
  146. package/core/frontend/services/proxy.js +10 -1
  147. package/core/frontend/services/routing/router-manager.js +14 -14
  148. package/core/frontend/services/routing/static-routes-router.js +8 -10
  149. package/core/server/adapters/storage/LocalFilesStorage.js +13 -11
  150. package/core/server/adapters/storage/LocalFilesStorage.ts +24 -0
  151. package/core/server/adapters/storage/LocalImagesStorage.js +14 -12
  152. package/core/server/adapters/storage/LocalImagesStorage.ts +24 -0
  153. package/core/server/adapters/storage/LocalMediaStorage.js +13 -11
  154. package/core/server/adapters/storage/LocalMediaStorage.ts +24 -0
  155. package/core/server/adapters/storage/LocalStorageBase.js +107 -167
  156. package/core/server/adapters/storage/LocalStorageBase.ts +342 -0
  157. package/core/server/adapters/storage/utils.js +36 -50
  158. package/core/server/adapters/storage/utils.ts +60 -0
  159. package/core/server/api/endpoints/automation-action-links.js +68 -0
  160. package/core/server/api/endpoints/automation-action-links.ts +46 -0
  161. package/core/server/api/endpoints/db.js +6 -0
  162. package/core/server/api/endpoints/index.js +4 -0
  163. package/core/server/api/endpoints/members.js +25 -28
  164. package/core/server/api/endpoints/pages.js +6 -1
  165. package/core/server/api/endpoints/posts.js +11 -2
  166. package/core/server/api/endpoints/settings.js +0 -2
  167. package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +4 -1
  168. package/core/server/api/endpoints/utils/serializers/output/members.js +18 -87
  169. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +11 -1
  170. package/core/server/data/exporter/export-filename.js +2 -1
  171. package/core/server/data/importer/importers/data/revue-subscriber-importer.js +6 -10
  172. package/core/server/data/importer/importers/json-to-html.js +24 -4
  173. package/core/server/data/migrations/versions/6.55/2026-07-27-11-25-44-add-to-hash-to-redirects.js +51 -0
  174. package/core/server/data/migrations/versions/6.55/2026-07-28-12-08-00-remove-routes-hash-from-settings.js +5 -0
  175. package/core/server/data/schema/commands.js +5 -0
  176. package/core/server/data/schema/default-settings/default-settings.json +0 -4
  177. package/core/server/data/schema/schema.js +5 -1
  178. package/core/server/lib/image/image-size.js +3 -17
  179. package/core/server/lib/image/image-utils.js +15 -1
  180. package/core/server/lib/request-external.js +1 -1
  181. package/core/server/models/user.js +46 -70
  182. package/core/server/services/auth/session/session-service.js +28 -1
  183. package/core/server/services/automations/automations-api.js +21 -6
  184. package/core/server/services/automations/automations-api.ts +25 -6
  185. package/core/server/services/automations/automations-repository.ts +19 -0
  186. package/core/server/services/automations/database-automations-repository.js +116 -28
  187. package/core/server/services/automations/database-automations-repository.ts +118 -9
  188. package/core/server/services/automations/database-date.js +10 -0
  189. package/core/server/services/automations/database-date.ts +5 -0
  190. package/core/server/services/automations/poll.js +5 -2
  191. package/core/server/services/automations/poll.ts +7 -2
  192. package/core/server/services/automations/scheduler-poll-time.js +40 -0
  193. package/core/server/services/automations/scheduler-poll-time.ts +38 -0
  194. package/core/server/services/automations/service.js +15 -3
  195. package/core/server/services/automations/service.ts +16 -3
  196. package/core/server/services/comments/comments-service-emails.js +14 -8
  197. package/core/server/services/comments/comments-service.js +9 -4
  198. package/core/server/services/email-rendering/partials/email-wrapper.hbs +3 -1
  199. package/core/server/services/email-service/email-service-wrapper.js +6 -1
  200. package/core/server/services/email-service/email-templates/partials/styles.hbs +8 -0
  201. package/core/server/services/email-service/newsletter-email-event-storage.js +41 -10
  202. package/core/server/services/lib/dynamic-redirect-manager.js +12 -3
  203. package/core/server/services/link-redirection/link-redirect-repository.js +41 -4
  204. package/core/server/services/link-redirection/link-redirect.js +2 -0
  205. package/core/server/services/link-redirection/link-redirect.ts +3 -1
  206. package/core/server/services/link-redirection/link-redirects-service.js +38 -2
  207. package/core/server/services/link-tracking/click-event.js +3 -0
  208. package/core/server/services/link-tracking/index.js +38 -6
  209. package/core/server/services/link-tracking/link-click-repository.js +30 -9
  210. package/core/server/services/link-tracking/link-click-tracking-service.js +44 -3
  211. package/core/server/services/member-attribution/outbound-link-tagger.js +3 -1
  212. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +19 -4
  213. package/core/server/services/member-welcome-emails/service.js +11 -3
  214. package/core/server/services/members/{importer → import-export}/config.js +4 -9
  215. package/core/server/services/members/{importer → import-export}/config.ts +4 -9
  216. package/core/server/services/members/import-export/csv/formula.js +16 -0
  217. package/core/server/services/members/import-export/csv/formula.ts +14 -0
  218. package/core/server/services/members/import-export/csv/index.js +12 -0
  219. package/core/server/services/members/import-export/csv/index.ts +3 -0
  220. package/core/server/services/members/import-export/csv/parse.js +64 -0
  221. package/core/server/services/members/import-export/csv/parse.ts +69 -0
  222. package/core/server/services/members/import-export/csv/serialize.js +13 -0
  223. package/core/server/services/members/import-export/csv/serialize.ts +18 -0
  224. package/core/server/services/members/import-export/export/exporter.js +202 -0
  225. package/core/server/services/members/import-export/export/exporter.ts +325 -0
  226. package/core/server/services/members/import-export/import/completion-email.js +84 -0
  227. package/core/server/services/members/import-export/import/completion-email.ts +141 -0
  228. package/core/server/services/members/import-export/import/importer.js +297 -0
  229. package/core/server/services/members/import-export/import/importer.ts +468 -0
  230. package/core/server/services/members/import-export/import/reader.js +32 -0
  231. package/core/server/services/members/import-export/import/reader.ts +33 -0
  232. package/core/server/services/members/import-export/import/row.js +33 -0
  233. package/core/server/services/members/import-export/import/row.ts +52 -0
  234. package/core/server/services/members/import-export/import/spool.js +30 -0
  235. package/core/server/services/members/import-export/import/spool.ts +38 -0
  236. package/core/server/services/members/import-export/index.js +99 -0
  237. package/core/server/services/members/import-export/index.ts +137 -0
  238. package/core/server/services/members/members-api/controllers/router-controller.js +7 -0
  239. package/core/server/services/members/service.js +25 -20
  240. package/core/server/services/members-custom-fields/values-service.js +19 -7
  241. package/core/server/services/members-custom-fields/values-service.ts +19 -7
  242. package/core/server/services/mentions/service.js +5 -1
  243. package/core/server/services/posts/posts-exporter.js +6 -4
  244. package/core/server/services/posts/posts-service.js +2 -2
  245. package/core/server/services/route-settings/activation-bridge.js +59 -49
  246. package/core/server/services/route-settings/activation-bridge.ts +91 -51
  247. package/core/server/services/route-settings/dynamic-routing-service.js +22 -51
  248. package/core/server/services/route-settings/index.js +1 -16
  249. package/core/server/services/settings/settings-service.js +0 -18
  250. package/core/server/services/themes/storage.js +9 -0
  251. package/core/server/services/themes/theme-storage.js +1 -1
  252. package/core/server/services/url/url-service-facade.js +1 -1
  253. package/core/server/services/url/url-service-facade.ts +8 -1
  254. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  255. package/core/shared/config/defaults.json +2 -1
  256. package/core/shared/express.js +6 -1
  257. package/core/shared/labs.js +2 -1
  258. package/package.json +11 -11
  259. package/pnpm-lock.yaml +18 -18
  260. package/pnpm-workspace.yaml +8 -7
  261. package/components/tryghost-adapter-base-scheduling-0.1.1.tgz +0 -0
  262. package/core/built/admin/assets/Overview-j4NaeUNm.js +0 -1
  263. package/core/built/admin/assets/Web-_fD935OX.js +0 -2
  264. package/core/built/admin/assets/activitypub/layout-IdVq3lRy.js +0 -1
  265. package/core/built/admin/assets/audience-BKRdsFmy.js +0 -1
  266. package/core/built/admin/assets/checkbox-DA4-1UiI.js +0 -1
  267. package/core/built/admin/assets/chunk.524.45e0875f41b71e46e849.js +0 -25
  268. package/core/built/admin/assets/chunk.582.51defd4b009f0de8d4d5.js +0 -27
  269. package/core/built/admin/assets/code-editor-view-BKBrkffZ.js +0 -1
  270. package/core/built/admin/assets/comments-CzVNIT8b.js +0 -1
  271. package/core/built/admin/assets/constants-c-JNF9c2.js +0 -1
  272. package/core/built/admin/assets/dist-CT46_f_J.js +0 -1
  273. package/core/built/admin/assets/domain-B7_n5nfx.js +0 -1
  274. package/core/built/admin/assets/dropzone-DcuJEEUf.js +0 -5
  275. package/core/built/admin/assets/editor-BCiIyFjm.js +0 -8
  276. package/core/built/admin/assets/field-BW-M2Zwp.js +0 -1
  277. package/core/built/admin/assets/filters-DtcXMZDU.js +0 -1
  278. package/core/built/admin/assets/gift-link-modal-Bk8qpnMw.js +0 -1
  279. package/core/built/admin/assets/growth-Ck2s3JjR.js +0 -1
  280. package/core/built/admin/assets/index-BBf502Wg.js +0 -69
  281. package/core/built/admin/assets/index-BCCZKxP2.css +0 -1
  282. package/core/built/admin/assets/input-group-DjuYnNCd.js +0 -1
  283. package/core/built/admin/assets/label-picker-BdNAWuUX.js +0 -1
  284. package/core/built/admin/assets/labels-Cyayfd-e.js +0 -1
  285. package/core/built/admin/assets/layout-Bw4GnxXp.js +0 -1
  286. package/core/built/admin/assets/member-custom-fields-DvFyamLu.js +0 -1
  287. package/core/built/admin/assets/member-detail-Cg2vTqvJ.js +0 -1
  288. package/core/built/admin/assets/members-DsPxYAVm.js +0 -10
  289. package/core/built/admin/assets/modals-Bf_fyvHZ.js +0 -31
  290. package/core/built/admin/assets/moderation-Dlp7M5sa.js +0 -1
  291. package/core/built/admin/assets/multi-select-combobox-CH1o8XXQ.js +0 -1
  292. package/core/built/admin/assets/newsletter-CQ5CAYUI.js +0 -1
  293. package/core/built/admin/assets/note-BrAZ7Z0o.js +0 -1
  294. package/core/built/admin/assets/notifications-BnbHLOPC.js +0 -1
  295. package/core/built/admin/assets/onboarding-route-eU146kuA.js +0 -1
  296. package/core/built/admin/assets/overview-BpbaK-xu.js +0 -1
  297. package/core/built/admin/assets/post-analytics-header-D9pJ4iNm.js +0 -1
  298. package/core/built/admin/assets/post-analytics-provider-BDqcfBor.js +0 -1
  299. package/core/built/admin/assets/profile-dJ44Bn_W.js +0 -1
  300. package/core/built/admin/assets/select--2uVN9_-.js +0 -1
  301. package/core/built/admin/assets/settings-SD9bxIpt.js +0 -4
  302. package/core/built/admin/assets/social-urls-BvFZwYcE.js +0 -8
  303. package/core/built/admin/assets/switch-qp518gFQ.js +0 -1
  304. package/core/built/admin/assets/tabs-D3ZOYkM7.js +0 -1
  305. package/core/built/admin/assets/textarea-CTlb3Uu6.js +0 -1
  306. package/core/built/admin/assets/use-filter-params-DeQ8sX4N.js +0 -1
  307. package/core/built/admin/assets/web-DTZnnUGr.js +0 -1
  308. package/core/server/services/mail/templates/newsletter.html +0 -876
  309. package/core/server/services/members/csv/index.js +0 -10
  310. package/core/server/services/members/csv/index.ts +0 -3
  311. package/core/server/services/members/csv/parse.js +0 -94
  312. package/core/server/services/members/csv/parse.ts +0 -111
  313. package/core/server/services/members/csv/types.js +0 -2
  314. package/core/server/services/members/csv/types.ts +0 -39
  315. package/core/server/services/members/csv/unparse.js +0 -69
  316. package/core/server/services/members/csv/unparse.ts +0 -79
  317. package/core/server/services/members/exporter/query.js +0 -233
  318. package/core/server/services/members/importer/index.js +0 -30
  319. package/core/server/services/members/importer/labels.js +0 -125
  320. package/core/server/services/members/importer/members-csv-importer.js +0 -520
  321. package/core/server/services/route-settings/default-settings-manager.js +0 -63
  322. package/core/server/services/route-settings/route-settings.js +0 -170
  323. package/core/server/services/route-settings/settings-loader.js +0 -60
  324. package/core/server/services/route-settings/settings-path-manager.js +0 -47
  325. package/core/server/services/route-settings/validate.js +0 -440
  326. /package/core/server/services/members/{importer → import-export/import}/email-template.js +0 -0
  327. /package/core/server/services/members/{importer/members-csv-importer-stripe-utils.js → import-export/import/stripe-utils.js} +0 -0
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.expandRouteSettings = expandRouteSettings;
6
+ exports.buildRouterSettings = buildRouterSettings;
7
7
  const lodash_1 = __importDefault(require("lodash"));
8
8
  const errors_1 = __importDefault(require("@tryghost/errors"));
9
9
  const config_1 = require("../../../frontend/services/routing/config");
@@ -87,78 +87,88 @@ function expandRouteData(routeData) {
87
87
  function convertSlugsToColons(value) {
88
88
  return value.replace(/{(\w+)}/g, ':$1');
89
89
  }
90
- function expandRoute(route) {
91
- const expanded = {};
92
- expanded.templates = route.templates || [];
93
- if (route.data !== undefined) {
94
- expanded.data = expandRouteData(route.data);
95
- }
90
+ function buildRouterRoute(route) {
91
+ const data = route.data !== undefined ? expandRouteData(route.data) : undefined;
92
+ // Build per branch: RouterRoute is a discriminated union, so each member is
93
+ // constructed as its concrete type. `route` is narrowed by the check.
96
94
  if (route.type === 'channel') {
97
- const channel = route;
98
- expanded.controller = 'channel';
99
- if (channel.filter !== undefined) {
100
- expanded.filter = channel.filter;
95
+ const result = {
96
+ path: route.path,
97
+ type: 'channel',
98
+ templates: route.templates || []
99
+ };
100
+ if (data !== undefined) {
101
+ result.data = data;
101
102
  }
102
- if (channel.order !== undefined) {
103
- expanded.order = channel.order;
103
+ if (route.filter !== undefined) {
104
+ result.filter = route.filter;
104
105
  }
105
- if (channel.limit !== undefined) {
106
- expanded.limit = channel.limit;
106
+ if (route.order !== undefined) {
107
+ result.order = route.order;
107
108
  }
108
- if (channel.rss !== undefined) {
109
- expanded.rss = channel.rss;
109
+ if (route.limit !== undefined) {
110
+ result.limit = route.limit;
110
111
  }
111
- }
112
- else {
113
- const template = route;
114
- if (template.contentType !== undefined) {
115
- expanded.content_type = template.contentType;
112
+ if (route.rss !== undefined) {
113
+ result.rss = route.rss;
116
114
  }
115
+ return result;
116
+ }
117
+ const result = {
118
+ path: route.path,
119
+ type: 'template',
120
+ templates: route.templates || []
121
+ };
122
+ if (data !== undefined) {
123
+ result.data = data;
117
124
  }
118
- return expanded;
125
+ if (route.contentType !== undefined) {
126
+ result.contentType = route.contentType;
127
+ }
128
+ return result;
119
129
  }
120
- function expandCollection(collection) {
121
- const expanded = {};
122
- expanded.permalink = convertSlugsToColons(collection.permalink);
123
- expanded.templates = collection.templates || [];
130
+ function buildRouterCollection(collection) {
131
+ const result = {
132
+ path: collection.path,
133
+ permalink: convertSlugsToColons(collection.permalink),
134
+ templates: collection.templates || []
135
+ };
124
136
  if (collection.data !== undefined) {
125
- expanded.data = expandRouteData(collection.data);
137
+ result.data = expandRouteData(collection.data);
126
138
  }
127
139
  if (collection.filter !== undefined) {
128
- expanded.filter = collection.filter;
140
+ result.filter = collection.filter;
129
141
  }
130
142
  if (collection.order !== undefined) {
131
- expanded.order = collection.order;
143
+ result.order = collection.order;
132
144
  }
133
145
  if (collection.limit !== undefined) {
134
- expanded.limit = collection.limit;
146
+ result.limit = collection.limit;
135
147
  }
136
148
  if (collection.rss !== undefined) {
137
- expanded.rss = collection.rss;
149
+ result.rss = collection.rss;
138
150
  }
139
- return expanded;
151
+ return result;
140
152
  }
141
153
  /**
142
- * Converts a RouteSettings domain model into the legacy expanded format
143
- * that routerManager.start() expects.
154
+ * Converts a RouteSettings domain model into the array shape that
155
+ * RouterManager.start() iterates. Each route/collection carries its own `path`,
156
+ * and taxonomies become `{key, permalink}` entries, so the routing layer no
157
+ * longer reads paths from map keys.
144
158
  *
145
- * This is a temporary adapter it gets removed once RouterManager is
146
- * refactored to consume the domain model directly (HKG-1895/HKG-1898).
159
+ * Temporary adapter: removed in HKG-1898 once the routers consume the domain
160
+ * model directly.
147
161
  */
148
- function expandRouteSettings(settings) {
149
- const routes = {};
150
- for (const route of settings.routes) {
151
- routes[route.path] = expandRoute(route);
152
- }
153
- const collections = {};
154
- for (const collection of settings.collections) {
155
- collections[collection.path] = expandCollection(collection);
156
- }
157
- const taxonomies = {};
162
+ function buildRouterSettings(settings) {
163
+ const taxonomies = [];
158
164
  for (const [key, value] of Object.entries(settings.taxonomies)) {
159
165
  if (value) {
160
- taxonomies[key] = convertSlugsToColons(value);
166
+ taxonomies.push({ key, permalink: convertSlugsToColons(value) });
161
167
  }
162
168
  }
163
- return { routes, collections, taxonomies };
169
+ return {
170
+ routes: settings.routes.map(buildRouterRoute),
171
+ collections: settings.collections.map(buildRouterCollection),
172
+ taxonomies
173
+ };
164
174
  }
@@ -121,90 +121,130 @@ function convertSlugsToColons(value: string): string {
121
121
  return value.replace(/{(\w+)}/g, ':$1');
122
122
  }
123
123
 
124
- function expandRoute(route: Route): Record<string, any> {
125
- const expanded: Record<string, any> = {};
124
+ /**
125
+ * Router-facing shapes. RouterManager consumes the domain model after the two
126
+ * conversions the bridge still applies: `data` expanded to `{query, router}`,
127
+ * and collection/taxonomy permalinks rewritten to `:slug`.
128
+ *
129
+ * Routes and collections are written as their domain counterpart with just
130
+ * `data` overridden to the expanded shape — `Omit<…, 'data'> & {data?}` rather
131
+ * than `extends`, because the override changes `data`'s type (interface
132
+ * extension can only add fields, not retype them). That keeps the delta from
133
+ * the domain model explicit: `data` is the only structural difference. When the
134
+ * bridge is removed (HKG-1898) the override falls away and these collapse back
135
+ * to `Route` / `CollectionConfig`.
136
+ */
137
+ type RouterChannelRoute = Omit<ChannelRoute, 'data'> & {data?: ExpandedData};
138
+ type RouterTemplateRoute = Omit<TemplateRoute, 'data'> & {data?: ExpandedData};
139
+ type RouterRoute = RouterChannelRoute | RouterTemplateRoute;
140
+
141
+ type RouterCollection = Omit<CollectionConfig, 'data'> & {data?: ExpandedData};
142
+
143
+ // Taxonomies and RouteSettings have no direct domain counterpart to derive from:
144
+ // the domain stores taxonomies as a `{tag, author}` map, which the bridge
145
+ // flattens into these `{key, permalink}` entries, and RouterSettings drops
146
+ // `yamlSource` and swaps all three array element types (routes, collections,
147
+ // taxonomies) — so they stay standalone.
148
+ interface RouterTaxonomy {
149
+ key: string;
150
+ permalink: string;
151
+ }
126
152
 
127
- expanded.templates = route.templates || [];
153
+ export interface RouterSettings {
154
+ routes: RouterRoute[];
155
+ collections: RouterCollection[];
156
+ taxonomies: RouterTaxonomy[];
157
+ }
128
158
 
129
- if (route.data !== undefined) {
130
- expanded.data = expandRouteData(route.data);
131
- }
159
+ function buildRouterRoute(route: Route): RouterRoute {
160
+ const data = route.data !== undefined ? expandRouteData(route.data) : undefined;
132
161
 
162
+ // Build per branch: RouterRoute is a discriminated union, so each member is
163
+ // constructed as its concrete type. `route` is narrowed by the check.
133
164
  if (route.type === 'channel') {
134
- const channel = route as ChannelRoute;
135
- expanded.controller = 'channel';
136
- if (channel.filter !== undefined) {
137
- expanded.filter = channel.filter;
165
+ const result: RouterChannelRoute = {
166
+ path: route.path,
167
+ type: 'channel',
168
+ templates: route.templates || []
169
+ };
170
+ if (data !== undefined) {
171
+ result.data = data;
138
172
  }
139
- if (channel.order !== undefined) {
140
- expanded.order = channel.order;
173
+ if (route.filter !== undefined) {
174
+ result.filter = route.filter;
141
175
  }
142
- if (channel.limit !== undefined) {
143
- expanded.limit = channel.limit;
176
+ if (route.order !== undefined) {
177
+ result.order = route.order;
144
178
  }
145
- if (channel.rss !== undefined) {
146
- expanded.rss = channel.rss;
179
+ if (route.limit !== undefined) {
180
+ result.limit = route.limit;
147
181
  }
148
- } else {
149
- const template = route as TemplateRoute;
150
- if (template.contentType !== undefined) {
151
- expanded.content_type = template.contentType;
182
+ if (route.rss !== undefined) {
183
+ result.rss = route.rss;
152
184
  }
185
+ return result;
153
186
  }
154
187
 
155
- return expanded;
188
+ const result: RouterTemplateRoute = {
189
+ path: route.path,
190
+ type: 'template',
191
+ templates: route.templates || []
192
+ };
193
+ if (data !== undefined) {
194
+ result.data = data;
195
+ }
196
+ if (route.contentType !== undefined) {
197
+ result.contentType = route.contentType;
198
+ }
199
+ return result;
156
200
  }
157
201
 
158
- function expandCollection(collection: CollectionConfig): Record<string, any> {
159
- const expanded: Record<string, any> = {};
160
-
161
- expanded.permalink = convertSlugsToColons(collection.permalink);
162
- expanded.templates = collection.templates || [];
202
+ function buildRouterCollection(collection: CollectionConfig): RouterCollection {
203
+ const result: RouterCollection = {
204
+ path: collection.path,
205
+ permalink: convertSlugsToColons(collection.permalink),
206
+ templates: collection.templates || []
207
+ };
163
208
 
164
209
  if (collection.data !== undefined) {
165
- expanded.data = expandRouteData(collection.data);
210
+ result.data = expandRouteData(collection.data);
166
211
  }
167
-
168
212
  if (collection.filter !== undefined) {
169
- expanded.filter = collection.filter;
213
+ result.filter = collection.filter;
170
214
  }
171
215
  if (collection.order !== undefined) {
172
- expanded.order = collection.order;
216
+ result.order = collection.order;
173
217
  }
174
218
  if (collection.limit !== undefined) {
175
- expanded.limit = collection.limit;
219
+ result.limit = collection.limit;
176
220
  }
177
221
  if (collection.rss !== undefined) {
178
- expanded.rss = collection.rss;
222
+ result.rss = collection.rss;
179
223
  }
180
224
 
181
- return expanded;
225
+ return result;
182
226
  }
183
227
 
184
228
  /**
185
- * Converts a RouteSettings domain model into the legacy expanded format
186
- * that routerManager.start() expects.
229
+ * Converts a RouteSettings domain model into the array shape that
230
+ * RouterManager.start() iterates. Each route/collection carries its own `path`,
231
+ * and taxonomies become `{key, permalink}` entries, so the routing layer no
232
+ * longer reads paths from map keys.
187
233
  *
188
- * This is a temporary adapter it gets removed once RouterManager is
189
- * refactored to consume the domain model directly (HKG-1895/HKG-1898).
234
+ * Temporary adapter: removed in HKG-1898 once the routers consume the domain
235
+ * model directly.
190
236
  */
191
- export function expandRouteSettings(settings: RouteSettings): {routes: Record<string, any>; collections: Record<string, any>; taxonomies: Record<string, string>} {
192
- const routes: Record<string, any> = {};
193
- for (const route of settings.routes) {
194
- routes[route.path] = expandRoute(route);
195
- }
196
-
197
- const collections: Record<string, any> = {};
198
- for (const collection of settings.collections) {
199
- collections[collection.path] = expandCollection(collection);
200
- }
201
-
202
- const taxonomies: Record<string, string> = {};
237
+ export function buildRouterSettings(settings: RouteSettings): RouterSettings {
238
+ const taxonomies: RouterTaxonomy[] = [];
203
239
  for (const [key, value] of Object.entries(settings.taxonomies)) {
204
240
  if (value) {
205
- taxonomies[key] = convertSlugsToColons(value);
241
+ taxonomies.push({key, permalink: convertSlugsToColons(value)});
206
242
  }
207
243
  }
208
244
 
209
- return {routes, collections, taxonomies};
245
+ return {
246
+ routes: settings.routes.map(buildRouterRoute),
247
+ collections: settings.collections.map(buildRouterCollection),
248
+ taxonomies
249
+ };
210
250
  }
@@ -1,5 +1,3 @@
1
- const crypto = require('crypto');
2
- const fs = require('fs-extra');
3
1
  const debug = require('@tryghost/debug')('services:route-settings:service');
4
2
  const logging = require('@tryghost/logging');
5
3
  const errors = require('@tryghost/errors');
@@ -9,12 +7,6 @@ const messages = {
9
7
  loadError: 'Could not load routes.yaml file.'
10
8
  };
11
9
 
12
- /**
13
- * md5 of the expanded default route settings — the routes_hash value for a
14
- * site that has never customised its routes.
15
- */
16
- const DEFAULT_ROUTES_SETTING_HASH = '3d180d52c663d173a6be791ef411ed01';
17
-
18
10
  function isStoredContentError(err) {
19
11
  return err.errorType === 'ValidationError' || err.errorType === 'IncorrectUsageError';
20
12
  }
@@ -27,26 +19,21 @@ class DynamicRoutingService {
27
19
  constructor() {
28
20
  /** @type {RouteSettingsStore} */
29
21
  this.store = null;
30
- this.settingsLoader = null;
31
22
  this.routerManager = null;
32
23
  this.urlService = null;
33
24
  }
34
25
 
35
26
  /**
36
- * Wire the storage-layer dependencies so the API surface (upload, download,
37
- * getCurrentHash) works immediately after boot — even when the frontend is
38
- * disabled and `start()` is never called.
27
+ * Wire the storage-layer dependency so the API surface (upload, download)
28
+ * works immediately after boot — even when the frontend is disabled and
29
+ * `start()` is never called.
39
30
  *
40
31
  * @param {object} deps
41
32
  * @param {RouteSettingsStore} deps.store - adapter-manager provided store
42
- * @param {object} [deps.settingsLoader] - legacy SettingsLoader used as a
43
- * read-path fallback when the stricter store parser rejects a file
44
- * (see loadRouteSettings)
45
33
  */
46
- configure({store, settingsLoader}) {
34
+ configure({store}) {
47
35
  debug('configure');
48
36
  this.store = store;
49
- this.settingsLoader = settingsLoader;
50
37
  }
51
38
 
52
39
  /**
@@ -67,49 +54,33 @@ class DynamicRoutingService {
67
54
  }
68
55
 
69
56
  async loadRouteSettings() {
70
- const {expandRouteSettings} = require('./activation-bridge');
57
+ const {buildRouterSettings} = require('./activation-bridge');
71
58
 
72
59
  try {
73
- return expandRouteSettings(await this.store.get());
60
+ return buildRouterSettings(await this.store.get());
74
61
  } catch (err) {
75
- if (!this.settingsLoader || err.errorType !== 'ValidationError') {
76
- throw err;
62
+ // A stored-content error means the site's routes.yaml is invalid —
63
+ // either it fails validation or it isn't parseable YAML. Log a
64
+ // targeted error so the failure is easy to spot in the logs, then
65
+ // rethrow so the caller surfaces the genuine error rather than
66
+ // silently degrading.
67
+ if (isStoredContentError(err)) {
68
+ logging.error(new errors.InternalServerError({
69
+ message: 'Route settings could not be loaded because the routes.yaml file is invalid. Please fix the file.',
70
+ code: 'ROUTE_SETTINGS_VALIDATION_ERROR',
71
+ err,
72
+ errorDetails: {reason: err.message}
73
+ }));
77
74
  }
78
- logging.error(new errors.InternalServerError({
79
- message: 'Route settings failed the stricter validation and were loaded with the legacy validator instead. Please review the routes.yaml file.',
80
- code: 'ROUTE_SETTINGS_VALIDATION_FALLBACK',
81
- err,
82
- errorDetails: {reason: err.message}
83
- }));
84
-
85
- return this.settingsLoader.loadSettings();
86
- }
87
- }
88
-
89
- getDefaultHash() {
90
- return DEFAULT_ROUTES_SETTING_HASH;
91
- }
92
75
 
93
- async getCurrentHash() {
94
- const expanded = await this.loadRouteSettings();
95
-
96
- return crypto.createHash('md5')
97
- .update(JSON.stringify(expanded), 'binary')
98
- .digest('hex');
76
+ throw err;
77
+ }
99
78
  }
100
79
 
101
80
  async download() {
102
- try {
103
- const settings = await this.store.get();
104
-
105
- return settings.yamlSource;
106
- } catch (err) {
107
- if (!this.settingsLoader || !isStoredContentError(err)) {
108
- throw err;
109
- }
81
+ const settings = await this.store.get();
110
82
 
111
- return fs.readFile(this.settingsLoader.settingFilePath, 'utf8');
112
- }
83
+ return settings.yamlSource;
113
84
  }
114
85
 
115
86
  async upload(yamlContent) {
@@ -1,5 +1,3 @@
1
- const config = require('../../../shared/config');
2
- const parseYaml = require('./yaml-parser');
3
1
  const DynamicRoutingService = require('./dynamic-routing-service');
4
2
 
5
3
  const service = new DynamicRoutingService();
@@ -7,15 +5,9 @@ const service = new DynamicRoutingService();
7
5
  module.exports = {
8
6
  init: async () => {
9
7
  const adapterManager = require('../adapter-manager').default;
10
- const SettingsLoader = require('./settings-loader');
11
- const SettingsPathManager = require('./settings-path-manager');
12
-
13
- const settingsPathManager = new SettingsPathManager({type: 'routes', paths: [config.getContentPath('settings')]});
14
- const settingsLoader = new SettingsLoader({parseYaml, settingFilePath: settingsPathManager.getDefaultFilePath()});
15
8
 
16
9
  service.configure({
17
- store: adapterManager.getAdapter('route-settings'),
18
- settingsLoader
10
+ store: adapterManager.getAdapter('route-settings')
19
11
  });
20
12
  },
21
13
 
@@ -27,10 +19,6 @@ module.exports = {
27
19
  return service.loadRouteSettings.bind(service);
28
20
  },
29
21
 
30
- get getDefaultHash() {
31
- return service.getDefaultHash.bind(service);
32
- },
33
-
34
22
  /**
35
23
  * Methods backing the Admin API settings endpoint — delegate to the
36
24
  * service instance so the endpoint stays decoupled from service wiring.
@@ -41,9 +29,6 @@ module.exports = {
41
29
  },
42
30
  get download() {
43
31
  return service.download.bind(service);
44
- },
45
- get getCurrentHash() {
46
- return service.getCurrentHash.bind(service);
47
32
  }
48
33
  }
49
34
  };
@@ -183,24 +183,6 @@ module.exports = {
183
183
  return fields;
184
184
  },
185
185
 
186
- /**
187
- * Handles synchronization of routes.yaml hash loaded in the frontend with
188
- * the value stored in the settings table.
189
- * getRoutesHash is a function to allow keeping "frontend" decoupled from settings
190
- *
191
- * @param {function} getRoutesHash function fetching currently loaded routes file hash
192
- */
193
- async syncRoutesHash(getRoutesHash) {
194
- const currentRoutesHash = await getRoutesHash();
195
-
196
- if (SettingsCache.get('routes_hash') !== currentRoutesHash) {
197
- return await models.Settings.edit([{
198
- key: 'routes_hash',
199
- value: currentRoutesHash
200
- }], {context: {internal: true}});
201
- }
202
- },
203
-
204
186
  /**
205
187
  * Handles email setting synchronization when email has been verified per instance
206
188
  *
@@ -27,6 +27,8 @@ const messages = {
27
27
  destroyActive: 'Deleting the active theme is not allowed.'
28
28
  };
29
29
 
30
+ const INVALID_THEME_REGEX = /^[./]*$/;
31
+
30
32
  let themeStorage;
31
33
 
32
34
  const getStorage = () => {
@@ -60,6 +62,13 @@ module.exports = {
60
62
  });
61
63
  }
62
64
 
65
+ // Reject theme names that only contain path elements.
66
+ if (INVALID_THEME_REGEX.test(themeName)) {
67
+ throw new errors.ValidationError({
68
+ message: 'Invalid theme name.'
69
+ });
70
+ }
71
+
63
72
  let checkedTheme;
64
73
  let overrideTheme;
65
74
  let renamedExisting = false;
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const config = require('../../../shared/config');
5
5
  const security = require('@tryghost/security');
6
6
  const {compress} = require('@tryghost/zip');
7
- const LocalStorageBase = require('../../adapters/storage/LocalStorageBase');
7
+ const LocalStorageBase = require('../../adapters/storage/LocalStorageBase').default;
8
8
 
9
9
  /**
10
10
  * @TODO: combine with loader.js?
@@ -49,7 +49,7 @@ class UrlServiceFacade {
49
49
  }
50
50
  const url = this.urlService.getUrlByResourceId(resource.id, options);
51
51
  if (this.isComparing() && !options?.skipComparison) {
52
- const context = this._compareContext(resource);
52
+ const context = this._compareContext(resource, options?.serializerContext ? { serializer: options.serializerContext } : {});
53
53
  // Snapshot: callers mutate the resource's nested objects in place
54
54
  // after this returns, but the comparison runs later via setImmediate.
55
55
  const snapshot = _.cloneDeep(resource);
@@ -43,6 +43,12 @@ export interface UrlOptions {
43
43
  // rows per rebuild would only capture stacks and recompute for nothing.
44
44
  // Dies with compare mode.
45
45
  skipComparison?: boolean;
46
+ // Diagnostic only: the producing api endpoint (docName/method/apiType),
47
+ // threaded from the output serializer so a compare mismatch or thin-resource
48
+ // throw names its producer in the logs. The compare-log caller stack
49
+ // truncates at the async api-framework boundary, so the stack alone can't.
50
+ // Ignored by both backends; only read into the compare context.
51
+ serializerContext?: {apiType?: string; docName?: string; method?: string};
46
52
  }
47
53
 
48
54
  /**
@@ -138,7 +144,8 @@ export class UrlServiceFacade {
138
144
  }
139
145
  const url = this.urlService.getUrlByResourceId(resource.id, options);
140
146
  if (this.isComparing() && !options?.skipComparison) {
141
- const context = this._compareContext(resource);
147
+ const context = this._compareContext(resource,
148
+ options?.serializerContext ? {serializer: options.serializerContext} : {});
142
149
  // Snapshot: callers mutate the resource's nested objects in place
143
150
  // after this returns, but the comparison runs later via setImmediate.
144
151
  const snapshot = _.cloneDeep(resource);
@@ -208,6 +208,7 @@ module.exports = function apiRoutes() {
208
208
 
209
209
  // ## Automations
210
210
  router.get('/automations', mw.authAdminApi, http(api.automations.browse));
211
+ router.get('/automations/:automation_id/actions/:action_id/links', mw.authAdminApi, http(api.automationActionLinks.browse));
211
212
  router.get('/automations/:id', mw.authAdminApi, http(api.automations.read));
212
213
  router.post('/automations/:id/email_preview', mw.authAdminApi, http(api.automationEmailPreviews.preview));
213
214
  router.post('/automations/:id/email_test', shared.middleware.brute.previewEmailLimiter, mw.authAdminApi, http(api.automationEmailPreviews.sendTestEmail));
@@ -366,5 +366,6 @@
366
366
  "captureLinkClickBadMemberUuid": false
367
367
  },
368
368
  "disableJSBackups": false,
369
- "disableMigrationBackups": false
369
+ "disableMigrationBackups": false,
370
+ "usingLoopbackReverseProxy": false
370
371
  }
@@ -2,6 +2,7 @@ const debug = require('@tryghost/debug')('shared:express');
2
2
  const express = require('express');
3
3
  const {createLazyRouter} = require('express-lazy-router');
4
4
  const sentry = require('./sentry');
5
+ const config = require('../../core/shared/config');
5
6
 
6
7
  const lazyLoad = createLazyRouter();
7
8
 
@@ -17,7 +18,11 @@ module.exports = (name) => {
17
18
 
18
19
  // Make sure 'req.secure' is valid for proxied requests
19
20
  // (X-Forwarded-Proto header will be checked, if present)
20
- app.enable('trust proxy');
21
+ if (config.get('usingLoopbackReverseProxy')) {
22
+ app.set('trust proxy', 'loopback');
23
+ } else {
24
+ app.enable('trust proxy');
25
+ }
21
26
 
22
27
  // Sentry must be our first error handler. Mounting it here means all custom error handlers will come after
23
28
  app.use(sentry.errorHandler);
@@ -64,7 +64,8 @@ const PRIVATE_FEATURES = [
64
64
  'getHelperDeduplication',
65
65
  'memberDetailsReact',
66
66
  'membersCustomFields',
67
- 'previewByTier'
67
+ 'previewByTier',
68
+ 'paywallImprovements'
68
69
  ];
69
70
 
70
71
  module.exports.GA_KEYS = [...GA_FEATURES];