ghost 6.9.1 → 6.9.3

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 (72) hide show
  1. package/components/tryghost-i18n-6.9.3.tgz +0 -0
  2. package/content/themes/source/package.json +1 -1
  3. package/core/built/admin/assets/activitypub/activitypub.js +2 -2
  4. package/core/built/admin/assets/activitypub/{index-C19nEXqT.mjs → index-D-0TWnTq.mjs} +2447 -2443
  5. package/core/built/admin/assets/activitypub/{index-B29oZuTp.mjs → index-Dz4ykJpN.mjs} +2 -2
  6. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-C0HG4CwA.mjs → CodeEditorView-C4K2SFCa.mjs} +2 -2
  7. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  8. package/core/built/admin/assets/admin-x-settings/{index-Cjl9AHTm.mjs → index-D1JNSNMf.mjs} +2 -2
  9. package/core/built/admin/assets/admin-x-settings/{index-DRpMCs_c.mjs → index-DLS4G2Af.mjs} +2 -2
  10. package/core/built/admin/assets/admin-x-settings/{index-Cz3Y1eRN.mjs → index-jt7mifza.mjs} +23310 -22917
  11. package/core/built/admin/assets/admin-x-settings/{modals-DXD3MDLp.mjs → modals-Bkrvssih.mjs} +8884 -8879
  12. package/core/built/admin/assets/{chunk.524.56454d8add89b041f4df.js → chunk.524.6d040cb21c767f0236ae.js} +7 -7
  13. package/core/built/admin/assets/{chunk.582.a4916bd533becb58fd9c.js → chunk.582.e35aed09ef750c552601.js} +10 -10
  14. package/core/built/admin/assets/ghost-192beb3c8f2f6e58b70d3aade481ae15.css +1 -0
  15. package/core/built/admin/assets/{ghost-94d0fbb20e8e880fa9ba144cf26ab050.js → ghost-5fd8d4d5e1ffe4d8405d30f3efba12b7.js} +105 -89
  16. package/core/built/admin/assets/ghost-dark-e50f4c063ecc977f023e78da2dd67b42.css +1 -0
  17. package/core/built/admin/assets/posts/posts.js +6745 -6741
  18. package/core/built/admin/assets/stats/stats.js +16015 -16011
  19. package/core/built/admin/index.html +4 -4
  20. package/core/server/data/tinybird/datasources/_mv_hits.datasource +4 -1
  21. package/core/server/data/tinybird/endpoints/README.md +109 -0
  22. package/core/server/data/tinybird/endpoints/api_kpis.pipe +2 -23
  23. package/core/server/data/tinybird/endpoints/api_monitoring_ingestion.pipe +14 -13
  24. package/core/server/data/tinybird/endpoints/api_monitoring_ingestion_aggregated.pipe +13 -12
  25. package/core/server/data/tinybird/endpoints/api_top_locations.pipe +0 -19
  26. package/core/server/data/tinybird/endpoints/api_top_pages.pipe +0 -23
  27. package/core/server/data/tinybird/endpoints/api_top_sources.pipe +0 -11
  28. package/core/server/data/tinybird/endpoints/api_top_utm_campaigns.pipe +0 -6
  29. package/core/server/data/tinybird/endpoints/api_top_utm_contents.pipe +0 -6
  30. package/core/server/data/tinybird/endpoints/api_top_utm_mediums.pipe +0 -6
  31. package/core/server/data/tinybird/endpoints/api_top_utm_sources.pipe +0 -6
  32. package/core/server/data/tinybird/endpoints/api_top_utm_terms.pipe +0 -6
  33. package/core/server/data/tinybird/pipes/filtered_sessions.pipe +28 -8
  34. package/core/server/data/tinybird/pipes/mv_hits.pipe +23 -11
  35. package/core/server/data/tinybird/tests/api_top_pages.yaml +5 -2
  36. package/core/server/services/email-service/DomainWarmingService.js +47 -9
  37. package/core/server/services/email-service/DomainWarmingService.ts +62 -11
  38. package/core/server/services/email-service/EmailServiceWrapper.js +2 -1
  39. package/core/server/services/member-welcome-emails/jobs/index.js +13 -9
  40. package/package.json +3 -3
  41. package/tsconfig.tsbuildinfo +1 -1
  42. package/yarn.lock +5 -50
  43. package/components/tryghost-i18n-6.9.1.tgz +0 -0
  44. package/core/built/admin/assets/ghost-dark-6c9cfa9c364e28c57e5983f68ec6f2fc.css +0 -1
  45. package/core/built/admin/assets/ghost-f724c1d53f5402f78a2d8cf8beb7c716.css +0 -1
  46. package/core/built/admin/assets/img/google-docs-1e42cc272fc088da49e4b0ddfb01b006.svg +0 -6
  47. package/core/built/admin/assets/img/mailchimp-f22b1e130aac764965b9306d7265a6b2.svg +0 -8
  48. package/core/built/admin/assets/img/patreon-b19a5e6418a72977a16b30039d374d04.svg +0 -7
  49. package/core/built/admin/assets/img/paypal-38e9448ce7549ea4caf8e7753ae661d6.svg +0 -8
  50. package/core/built/admin/assets/img/slackicon-406aadea8994ca2ddee9c1d7157208db.png +0 -0
  51. package/core/built/admin/assets/img/themes/Alto-0dfe76694ed222d6d96fc9c8db979a38.png +0 -0
  52. package/core/built/admin/assets/img/themes/Bulletin-d66dec818ad0ba2965dd7eb3130c621c.png +0 -0
  53. package/core/built/admin/assets/img/themes/Casper-9a0ce71df3a1c589c1414ad2aa5b8aeb.png +0 -0
  54. package/core/built/admin/assets/img/themes/Dawn-302fbfdbc352098a256137159bd83dd8.png +0 -0
  55. package/core/built/admin/assets/img/themes/Digest-698e78d8e8481daff8ae4a8647528dc9.png +0 -0
  56. package/core/built/admin/assets/img/themes/Dope-d099dfca697adae16baa76f89520c5b3.png +0 -0
  57. package/core/built/admin/assets/img/themes/Ease-7075f809892f10c58892e15a57a4aae4.png +0 -0
  58. package/core/built/admin/assets/img/themes/Edge-1e5e0eec6941d7bdca02cebb66187357.png +0 -0
  59. package/core/built/admin/assets/img/themes/Edition-10111a2b8458168dcff81b7fb151be70.png +0 -0
  60. package/core/built/admin/assets/img/themes/Episode-e4c86d1f75ef1d8a77791d7fd519fdd4.png +0 -0
  61. package/core/built/admin/assets/img/themes/Headline-f70eaf49b9fcae1ddfe3d4496d8be54d.png +0 -0
  62. package/core/built/admin/assets/img/themes/Journal-07d35b2311501d2738bad1907ba2f7e1.png +0 -0
  63. package/core/built/admin/assets/img/themes/London-4e042390da16fecef947f3a7001d03db.png +0 -0
  64. package/core/built/admin/assets/img/themes/Ruby-b896885448e0f28ca62c6dcd56c732aa.png +0 -0
  65. package/core/built/admin/assets/img/themes/Solo-0292eb9ae0ca7b578cff50824d40cc86.png +0 -0
  66. package/core/built/admin/assets/img/themes/Source-Magazine-176385eb99561485ce6385598f7599b0.png +0 -0
  67. package/core/built/admin/assets/img/themes/Source-Newsletter-bb1a8611a326edb78f9ecb8bc1838c81.png +0 -0
  68. package/core/built/admin/assets/img/themes/Source-f29c6d6abe774ea7d5940a7431069fce.png +0 -0
  69. package/core/built/admin/assets/img/themes/Taste-a24d2a786900d9caff7e773ca0040991.png +0 -0
  70. package/core/built/admin/assets/img/themes/Wave-b98fadfd3ed16e8b2e383dd8e8e5dca2.png +0 -0
  71. package/core/built/admin/assets/img/typeform-9f23f8712d776a7515594676285266f5.svg +0 -7
  72. package/core/built/admin/assets/img/zero-bounce-ee799eddb1f88e33ab8c462858cbfed9.png +0 -0
@@ -1,32 +1,62 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DomainWarmingService = void 0;
4
+ /**
5
+ * Configuration for domain warming email volume scaling.
6
+ *
7
+ * | Volume Range | Multiplier |
8
+ * |--------------|--------------------------------------------------|
9
+ * | ≤100 (base) | 200 messages |
10
+ * | 101 – 1k | 1.25× (conservative early ramp) |
11
+ * | 1k – 5k | 1.5× (moderate increase) |
12
+ * | 5k – 100k | 1.75× (faster ramp after proving deliverability) |
13
+ * | 100k – 400k | 2× |
14
+ * | 400k+ | min(1.2×, +75k) cap |
15
+ */
4
16
  const WARMUP_SCALING_TABLE = {
5
17
  base: {
6
18
  limit: 100,
7
19
  value: 200
8
20
  },
9
21
  thresholds: [{
22
+ limit: 1_000,
23
+ scale: 1.25
24
+ }, {
25
+ limit: 5_000,
26
+ scale: 1.5
27
+ }, {
10
28
  limit: 100_000,
11
- scale: 2
29
+ scale: 1.75
12
30
  }, {
13
31
  limit: 400_000,
14
- scale: 1.5
32
+ scale: 2
15
33
  }],
16
- defaultScale: 1.25
34
+ highVolume: {
35
+ threshold: 400_000,
36
+ maxScale: 1.2,
37
+ maxAbsoluteIncrease: 75_000
38
+ }
17
39
  };
18
40
  class DomainWarmingService {
19
41
  #emailModel;
20
42
  #labs;
43
+ #config;
21
44
  constructor(dependencies) {
22
45
  this.#emailModel = dependencies.models.Email;
23
46
  this.#labs = dependencies.labs;
47
+ this.#config = dependencies.config;
24
48
  }
25
49
  /**
26
50
  * @returns Whether the domain warming feature is enabled
27
51
  */
28
52
  isEnabled() {
29
- return this.#labs.isSet('domainWarmup');
53
+ const hasLabsFlag = this.#labs.isSet('domainWarmup');
54
+ if (!hasLabsFlag) {
55
+ return false;
56
+ }
57
+ const fallbackDomain = this.#config.get('hostSettings:managedEmail:fallbackDomain');
58
+ const fallbackAddress = this.#config.get('hostSettings:managedEmail:fallbackAddress');
59
+ return Boolean(fallbackDomain && fallbackAddress);
30
60
  }
31
61
  /**
32
62
  * Get the maximum amount of emails that should be sent from the warming sending domain in today's newsletter
@@ -41,14 +71,15 @@ class DomainWarmingService {
41
71
  * @returns The highest number of messages sent from the CSD in a single email (excluding today)
42
72
  */
43
73
  async #getHighestCount() {
44
- const email = await this.#emailModel.findOne({
74
+ const result = await this.#emailModel.findPage({
45
75
  filter: `created_at:<${new Date().toISOString().split('T')[0]}`,
46
- order: 'csd_email_count DESC'
76
+ order: 'csd_email_count DESC',
77
+ limit: 1
47
78
  });
48
- if (!email) {
79
+ if (!result.data.length) {
49
80
  return 0;
50
81
  }
51
- const count = email.get('csd_email_count');
82
+ const count = result.data[0].get('csd_email_count');
52
83
  return count || 0;
53
84
  }
54
85
  /**
@@ -59,12 +90,19 @@ class DomainWarmingService {
59
90
  if (lastCount <= WARMUP_SCALING_TABLE.base.limit) {
60
91
  return WARMUP_SCALING_TABLE.base.value;
61
92
  }
93
+ // For high volume senders (400k+), cap the increase at 20% or 75k absolute
94
+ if (lastCount > WARMUP_SCALING_TABLE.highVolume.threshold) {
95
+ const scaledIncrease = Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale);
96
+ const absoluteIncrease = lastCount + WARMUP_SCALING_TABLE.highVolume.maxAbsoluteIncrease;
97
+ return Math.min(scaledIncrease, absoluteIncrease);
98
+ }
62
99
  for (const threshold of WARMUP_SCALING_TABLE.thresholds.sort((a, b) => a.limit - b.limit)) {
63
100
  if (lastCount <= threshold.limit) {
64
101
  return Math.ceil(lastCount * threshold.scale);
65
102
  }
66
103
  }
67
- return Math.ceil(lastCount * WARMUP_SCALING_TABLE.defaultScale);
104
+ // This should not be reached given the thresholds cover all cases up to highVolume.threshold
105
+ return Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale);
68
106
  }
69
107
  }
70
108
  exports.DomainWarmingService = DomainWarmingService;
@@ -2,8 +2,12 @@ type LabsService = {
2
2
  isSet: (flag: string) => boolean;
3
3
  };
4
4
 
5
+ type ConfigService = {
6
+ get: (key: string) => string | undefined;
7
+ }
8
+
5
9
  type EmailModel = {
6
- findOne: (options: {filter: string; order: string}) => Promise<EmailRecord | null>;
10
+ findPage: (options: {filter: string; order: string; limit: number}) => Promise<{data: EmailRecord[]}>;
7
11
  };
8
12
 
9
13
  type EmailRecord = {
@@ -20,41 +24,79 @@ type WarmupScalingTable = {
20
24
  limit: number;
21
25
  scale: number;
22
26
  }[];
23
- defaultScale: number;
27
+ highVolume: {
28
+ threshold: number;
29
+ maxScale: number;
30
+ maxAbsoluteIncrease: number;
31
+ };
24
32
  }
25
33
 
34
+ /**
35
+ * Configuration for domain warming email volume scaling.
36
+ *
37
+ * | Volume Range | Multiplier |
38
+ * |--------------|--------------------------------------------------|
39
+ * | ≤100 (base) | 200 messages |
40
+ * | 101 – 1k | 1.25× (conservative early ramp) |
41
+ * | 1k – 5k | 1.5× (moderate increase) |
42
+ * | 5k – 100k | 1.75× (faster ramp after proving deliverability) |
43
+ * | 100k – 400k | 2× |
44
+ * | 400k+ | min(1.2×, +75k) cap |
45
+ */
26
46
  const WARMUP_SCALING_TABLE: WarmupScalingTable = {
27
47
  base: {
28
48
  limit: 100,
29
49
  value: 200
30
50
  },
31
51
  thresholds: [{
52
+ limit: 1_000,
53
+ scale: 1.25
54
+ }, {
55
+ limit: 5_000,
56
+ scale: 1.5
57
+ }, {
32
58
  limit: 100_000,
33
- scale: 2
59
+ scale: 1.75
34
60
  }, {
35
61
  limit: 400_000,
36
- scale: 1.5
62
+ scale: 2
37
63
  }],
38
- defaultScale: 1.25
64
+ highVolume: {
65
+ threshold: 400_000,
66
+ maxScale: 1.2,
67
+ maxAbsoluteIncrease: 75_000
68
+ }
39
69
  };
40
70
 
41
71
  export class DomainWarmingService {
42
72
  #emailModel: EmailModel;
43
73
  #labs: LabsService;
74
+ #config: ConfigService;
44
75
 
45
76
  constructor(dependencies: {
46
77
  models: {Email: EmailModel};
47
78
  labs: LabsService;
79
+ config: ConfigService;
48
80
  }) {
49
81
  this.#emailModel = dependencies.models.Email;
50
82
  this.#labs = dependencies.labs;
83
+ this.#config = dependencies.config;
51
84
  }
52
85
 
53
86
  /**
54
87
  * @returns Whether the domain warming feature is enabled
55
88
  */
56
89
  isEnabled(): boolean {
57
- return this.#labs.isSet('domainWarmup');
90
+ const hasLabsFlag = this.#labs.isSet('domainWarmup');
91
+
92
+ if (!hasLabsFlag) {
93
+ return false;
94
+ }
95
+
96
+ const fallbackDomain = this.#config.get('hostSettings:managedEmail:fallbackDomain');
97
+ const fallbackAddress = this.#config.get('hostSettings:managedEmail:fallbackAddress');
98
+
99
+ return Boolean(fallbackDomain && fallbackAddress);
58
100
  }
59
101
 
60
102
  /**
@@ -72,16 +114,17 @@ export class DomainWarmingService {
72
114
  * @returns The highest number of messages sent from the CSD in a single email (excluding today)
73
115
  */
74
116
  async #getHighestCount(): Promise<number> {
75
- const email = await this.#emailModel.findOne({
117
+ const result = await this.#emailModel.findPage({
76
118
  filter: `created_at:<${new Date().toISOString().split('T')[0]}`,
77
- order: 'csd_email_count DESC'
119
+ order: 'csd_email_count DESC',
120
+ limit: 1
78
121
  });
79
122
 
80
- if (!email) {
123
+ if (!result.data.length) {
81
124
  return 0;
82
125
  }
83
126
 
84
- const count = email.get('csd_email_count');
127
+ const count = result.data[0].get('csd_email_count');
85
128
  return count || 0;
86
129
  }
87
130
 
@@ -94,12 +137,20 @@ export class DomainWarmingService {
94
137
  return WARMUP_SCALING_TABLE.base.value;
95
138
  }
96
139
 
140
+ // For high volume senders (400k+), cap the increase at 20% or 75k absolute
141
+ if (lastCount > WARMUP_SCALING_TABLE.highVolume.threshold) {
142
+ const scaledIncrease = Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale);
143
+ const absoluteIncrease = lastCount + WARMUP_SCALING_TABLE.highVolume.maxAbsoluteIncrease;
144
+ return Math.min(scaledIncrease, absoluteIncrease);
145
+ }
146
+
97
147
  for (const threshold of WARMUP_SCALING_TABLE.thresholds.sort((a, b) => a.limit - b.limit)) {
98
148
  if (lastCount <= threshold.limit) {
99
149
  return Math.ceil(lastCount * threshold.scale);
100
150
  }
101
151
  }
102
152
 
103
- return Math.ceil(lastCount * WARMUP_SCALING_TABLE.defaultScale);
153
+ // This should not be reached given the thresholds cover all cases up to highVolume.threshold
154
+ return Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale);
104
155
  }
105
156
  }
@@ -118,7 +118,8 @@ class EmailServiceWrapper {
118
118
 
119
119
  const domainWarmingService = new DomainWarmingService({
120
120
  models: {Email},
121
- labs
121
+ labs,
122
+ config: configService
122
123
  });
123
124
 
124
125
  const batchSendingService = new BatchSendingService({
@@ -8,20 +8,24 @@ let hasScheduled = {
8
8
 
9
9
  module.exports = {
10
10
  async scheduleMemberWelcomeEmailJob() {
11
- if (!config.get('memberWelcomeEmailTestInbox')) {
11
+ const testInboxDisabled = !config.get('memberWelcomeEmailTestInbox');
12
+ const alreadyScheduledProcessing = hasScheduled.processOutbox;
13
+
14
+ if (testInboxDisabled || alreadyScheduledProcessing) {
12
15
  return false;
13
16
  }
14
17
 
15
- if (!hasScheduled.processOutbox && !process.env.NODE_ENV.startsWith('test')) {
16
- jobsService.addJob({
17
- at: '0 */5 * * * *',
18
- job: path.resolve(__dirname, 'process-outbox.js'),
19
- name: 'process-member-welcome-emails'
20
- });
18
+ const configValue = config.get('memberWelcomeEmailSendInstantly');
19
+ const testEmailSendInstantly = configValue === true || configValue === 'true';
20
+ const cronSchedule = testEmailSendInstantly ? '*/3 * * * * *' : '0 */5 * * * *';
21
21
 
22
- hasScheduled.processOutbox = true;
23
- }
22
+ jobsService.addJob({
23
+ at: cronSchedule,
24
+ job: path.resolve(__dirname, 'process-outbox.js'),
25
+ name: 'process-member-welcome-emails'
26
+ });
24
27
 
28
+ hasScheduled.processOutbox = true;
25
29
  return hasScheduled.processOutbox;
26
30
  }
27
31
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost",
3
- "version": "6.9.1",
3
+ "version": "6.9.3",
4
4
  "description": "The professional publishing platform",
5
5
  "author": "Ghost Foundation",
6
6
  "homepage": "https://ghost.org",
@@ -87,7 +87,7 @@
87
87
  "@tryghost/helpers": "1.1.97",
88
88
  "@tryghost/html-to-plaintext": "1.0.4",
89
89
  "@tryghost/http-cache-utils": "0.1.20",
90
- "@tryghost/i18n": "file:components/tryghost-i18n-6.9.1.tgz",
90
+ "@tryghost/i18n": "file:components/tryghost-i18n-6.9.3.tgz",
91
91
  "@tryghost/image-transform": "1.4.6",
92
92
  "@tryghost/job-manager": "1.0.3",
93
93
  "@tryghost/kg-card-factory": "5.1.2",
@@ -274,7 +274,7 @@
274
274
  "jackspeak": "2.3.6",
275
275
  "moment": "2.24.0",
276
276
  "moment-timezone": "0.5.45",
277
- "@tryghost/i18n": "file:components/tryghost-i18n-6.9.1.tgz"
277
+ "@tryghost/i18n": "file:components/tryghost-i18n-6.9.3.tgz"
278
278
  },
279
279
  "nx": {
280
280
  "targets": {