From f688c7799075ae31c5765208ddcb2d4d08769b19 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 23 Apr 2026 13:12:11 -0500 Subject: [PATCH 1/4] Bailed out of welcome email automation if member status changes (#27537) closes https://linear.app/ghost/issue/NY-1194 --- .../welcome-email-automations/poll.js | 6 ++- .../welcome-email-automations/poll.test.js | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/ghost/core/core/server/services/welcome-email-automations/poll.js b/ghost/core/core/server/services/welcome-email-automations/poll.js index d7b14f227b8..3c69eec9fd4 100644 --- a/ghost/core/core/server/services/welcome-email-automations/poll.js +++ b/ghost/core/core/server/services/welcome-email-automations/poll.js @@ -199,7 +199,11 @@ async function processRun({ } // TODO(NY-1193): Bail if member is unsubscribed - // TODO(NY-1194): Bail if member's status has changed + + if (member.get('status') !== memberStatus) { + await markExited(run.id, 'member changed status'); + return; + } await memberWelcomeEmailService.api.send({ member: { diff --git a/ghost/core/test/integration/services/welcome-email-automations/poll.test.js b/ghost/core/test/integration/services/welcome-email-automations/poll.test.js index 150780122e0..4a3640be29a 100644 --- a/ghost/core/test/integration/services/welcome-email-automations/poll.test.js +++ b/ghost/core/test/integration/services/welcome-email-automations/poll.test.js @@ -429,6 +429,43 @@ describe('welcome email automations poll', function () { assert.deepEqual(await readTrackedRecipients(), []); }); + it('bails out if member status has changed since the run was created', async function () { + const automation = await createAutomation({ + slug: MEMBER_WELCOME_EMAIL_SLUGS.free + }); + const automatedEmail = await createAutomatedEmail({ + welcome_email_automation_id: automation.id + }); + const member = await createMember({ + status: 'free' + }); + const run = await createRun({ + welcome_email_automation_id: automation.id, + member_id: member.id, + next_welcome_email_automated_email_id: automatedEmail.id, + ready_at: new Date(Date.now() - 1000) + }); + + await testUtils.knex('members').where({id: member.id}).update({ + status: 'paid', + updated_at: new Date() + }); + + await poll(options); + + sinon.assert.notCalled(options.memberWelcomeEmailService.api.send); + sinon.assert.notCalled(options.enqueueAnotherPollNow); + sinon.assert.notCalled(options.enqueueAnotherPollAt); + assert.deepEqual(await readTrackedRecipients(), []); + + const updatedRun = await readRun(run.id); + assert.equal(updatedRun.exit_reason, 'member changed status'); + assert.equal(updatedRun.next_welcome_email_automated_email_id, null); + assert.equal(updatedRun.ready_at, null); + assert.equal(updatedRun.step_started_at, null); + assert.equal(updatedRun.step_attempts, 0); + }); + it('fails if run stored with more attempts than now supported', async function () { const automation = await createAutomation(); const automatedEmail = await createAutomatedEmail({ From d0650e95891ab3e22a7c01d85e98997728713b60 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 23 Apr 2026 13:42:41 -0500 Subject: [PATCH 2/4] Removed useless Comment model test (#27540) no ref This test had no assertions and can easily be removed. --- ghost/core/test/unit/server/models/comment.test.js | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 ghost/core/test/unit/server/models/comment.test.js diff --git a/ghost/core/test/unit/server/models/comment.test.js b/ghost/core/test/unit/server/models/comment.test.js deleted file mode 100644 index 2b9dc02f14c..00000000000 --- a/ghost/core/test/unit/server/models/comment.test.js +++ /dev/null @@ -1,12 +0,0 @@ -const sinon = require('sinon'); -const models = require('../../../../core/server/models'); - -describe('Unit: models/comment', function () { - before(function () { - models.init(); - }); - - afterEach(function () { - sinon.restore(); - }); -}); From 1c6358296681ea802927fe520130f53eba23e10b Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Thu, 23 Apr 2026 13:53:53 -0500 Subject: [PATCH 3/4] Bumped @tryghost/logging to 4.1.0 (#27541) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Bumps `@tryghost/logging` from `2.5.5` to `4.1.0` across the three places it's pinned: - `ghost/core/package.json` - `e2e/package.json` - root `pnpm.overrides` ## Why Catching Ghost up on `@tryghost/*` framework bumps. Logging is a leaf dependency (dev/runtime tooling only, no public API surface exposed through Ghost), so this is a low-blast-radius bump. `@tryghost/logging` v3 and v4 are engine-floor bumps — they drop support for Node 14/16/18 and modernize internals. Ghost's own `engines` already requires Node 20+, so there's no runtime impact. --- e2e/package.json | 2 +- ghost/core/package.json | 2 +- package.json | 2 +- pnpm-lock.yaml | 585 +++++++++++++++++++++++++++------------- 4 files changed, 399 insertions(+), 192 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 96a0870c55f..65cc03d1779 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -32,7 +32,7 @@ "@faker-js/faker": "8.4.1", "@playwright/test": "1.59.1", "@tryghost/debug": "2.1.0", - "@tryghost/logging": "2.5.5", + "@tryghost/logging": "4.1.0", "@types/dockerode": "3.3.47", "@types/express": "4.17.25", "busboy": "^1.6.0", diff --git a/ghost/core/package.json b/ghost/core/package.json index d38c52f3a24..834e9b3f1fa 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -119,7 +119,7 @@ "@tryghost/kg-markdown-html-renderer": "7.2.0", "@tryghost/kg-mobiledoc-html-renderer": "7.2.0", "@tryghost/limit-service": "1.5.2", - "@tryghost/logging": "2.5.5", + "@tryghost/logging": "4.1.0", "@tryghost/members-csv": "2.0.5", "@tryghost/metrics": "1.0.43", "@tryghost/mongo-utils": "0.6.3", diff --git a/package.json b/package.json index 84b845a933c..857ce21078f 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "pnpm": { "overrides": { "@tryghost/errors": "^1.3.7", - "@tryghost/logging": "2.5.5", + "@tryghost/logging": "4.1.0", "jackspeak": "2.3.6", "moment": "2.24.0", "moment-timezone": "0.5.45", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 718a56b03f4..0425b931a69 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ catalogs: overrides: '@tryghost/errors': ^1.3.7 - '@tryghost/logging': 2.5.5 + '@tryghost/logging': 4.1.0 jackspeak: 2.3.6 moment: 2.24.0 moment-timezone: 0.5.45 @@ -79,7 +79,7 @@ importers: version: 16.4.0 nx: specifier: 22.0.4 - version: 22.0.4(@swc/core@1.15.21) + version: 22.0.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) rimraf: specifier: 6.1.3 version: 6.1.3 @@ -158,13 +158,13 @@ importers: version: 18.3.7(@types/react@18.3.28) jest: specifier: 29.7.0 - version: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + version: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 ts-jest: specifier: 29.4.9 - version: 29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3) vite: specifier: 5.4.21 version: 5.4.21(@types/node@25.6.0)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1) @@ -234,7 +234,7 @@ importers: version: 18.3.7(@types/react@18.3.28) '@vitejs/plugin-react-swc': specifier: 4.1.0 - version: 4.1.0(vite@7.1.12(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.0(@swc/helpers@0.5.21)(vite@7.1.12(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) eslint: specifier: catalog:eslint9 version: 9.37.0(jiti@2.6.1) @@ -255,7 +255,7 @@ importers: version: 17.4.0 jest-extended: specifier: 7.0.0 - version: 7.0.0(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3) + version: 7.0.0(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3) jsdom: specifier: 28.1.0 version: 28.1.0(@noble/hashes@1.8.0) @@ -364,7 +364,7 @@ importers: version: 8.6.14(react@18.3.1)(storybook@8.6.15(prettier@2.8.8)) '@storybook/addon-styling': specifier: 1.3.7 - version: 1.3.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(encoding@0.1.13)(less@4.6.4)(postcss@8.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)) + version: 1.3.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(encoding@0.1.13)(less@4.6.4)(postcss@8.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)) '@storybook/blocks': specifier: 8.6.14 version: 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@2.8.8)) @@ -1175,13 +1175,13 @@ importers: version: 6.4.11 '@storybook/addon-docs': specifier: 10.3.3 - version: 10.3.3(@types/react@18.3.28)(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4)) + version: 10.3.3(@types/react@18.3.28)(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)) '@storybook/addon-links': specifier: 10.3.3 version: 10.3.3(react@18.3.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@storybook/react-vite': specifier: 10.3.3 - version: 10.3.3(esbuild@0.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4)) + version: 10.3.3(esbuild@0.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)) '@tailwindcss/postcss': specifier: 4.2.1 version: 4.2.1 @@ -1296,7 +1296,7 @@ importers: version: 8.6.14(react@18.3.1)(storybook@8.6.15(prettier@2.8.8)) '@storybook/addon-styling': specifier: 1.3.7 - version: 1.3.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(encoding@0.1.13)(less@4.6.4)(postcss@8.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21)) + version: 1.3.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(encoding@0.1.13)(less@4.6.4)(postcss@8.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) '@storybook/blocks': specifier: 8.6.14 version: 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.15(prettier@2.8.8)) @@ -1522,8 +1522,8 @@ importers: specifier: 2.1.0 version: 2.1.0 '@tryghost/logging': - specifier: 2.5.5 - version: 2.5.5 + specifier: 4.1.0 + version: 4.1.0 '@types/dockerode': specifier: 3.3.47 version: 3.3.47 @@ -1565,7 +1565,7 @@ importers: version: 8.222.0 ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3) + version: 10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3) typescript: specifier: 5.9.3 version: 5.9.3 @@ -1586,7 +1586,7 @@ importers: version: 1.0.1 webpack: specifier: 5.105.4 - version: 5.105.4(@swc/core@1.15.21) + version: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) devDependencies: '@babel/eslint-parser': specifier: 7.28.4 @@ -1626,7 +1626,7 @@ importers: version: 3.0.0(@babel/core@7.29.0) '@sentry/ember': specifier: 7.120.3 - version: 7.120.3(webpack@5.105.4(@swc/core@1.15.21)) + version: 7.120.3(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) '@sentry/integrations': specifier: 7.114.0 version: 7.114.0 @@ -1722,7 +1722,7 @@ importers: version: 0.5.0(ember-source@3.24.0(@babel/core@7.29.0)) ember-auto-import: specifier: 2.10.0 - version: 2.10.0(webpack@5.105.4(@swc/core@1.15.21)) + version: 2.10.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) ember-classic-decorator: specifier: 3.0.1 version: 3.0.1 @@ -1809,10 +1809,10 @@ importers: version: 8.1.2(encoding@0.1.13) ember-in-viewport: specifier: 4.1.0 - version: 4.1.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21)) + version: 4.1.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) ember-infinity: specifier: 2.3.0 - version: 2.3.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21)) + version: 2.3.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) ember-keyboard: specifier: 8.2.1 version: 8.2.1(@babel/core@7.29.0)(@ember/test-helpers@2.9.6(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0)))(ember-source@3.24.0(@babel/core@7.29.0)) @@ -2078,8 +2078,8 @@ importers: specifier: 1.5.2 version: 1.5.2 '@tryghost/logging': - specifier: 2.5.5 - version: 2.5.5 + specifier: 4.1.0 + version: 4.1.0 '@tryghost/members-csv': specifier: 2.0.5 version: 2.0.5 @@ -2530,7 +2530,7 @@ importers: version: 4.0.0 html-validate: specifier: 8.29.0 - version: 8.29.0(jest-diff@29.7.0)(jest-snapshot@29.7.0)(jest@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)))(vitest@1.6.1(@types/node@22.19.17)(jsdom@28.1.0(@noble/hashes@1.8.0))(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)) + version: 8.29.0(jest-diff@29.7.0)(jest-snapshot@29.7.0)(jest@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)))(vitest@1.6.1(@types/node@22.19.17)(jsdom@28.1.0(@noble/hashes@1.8.0))(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)) inquirer: specifier: 8.2.7 version: 8.2.7(@types/node@22.19.17) @@ -2638,7 +2638,7 @@ importers: version: 21.0.1 ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3) + version: 10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3) tsx: specifier: 4.21.0 version: 4.21.0 @@ -3892,10 +3892,18 @@ packages: resolution: {integrity: sha512-2G4Vu6OHw4+XTrp7AGIcOEezpPEoVrWg2JTK1v/exEKSLYquZkUdd+m4yOL3/UZ6bTj7hmXwrmYzW76BnLCkJQ==} engines: {node: '>=18'} + '@elastic/elasticsearch@9.3.4': + resolution: {integrity: sha512-Mp14fPEYx+WTfZdcvAaZ9WkLYGHQCbwMx6EP5VCucYdhv4cn/g2sbnMT5HzK+gX3XEpBnnkEK/+WysCKzxuo3A==} + engines: {node: '>=18'} + '@elastic/transport@8.4.1': resolution: {integrity: sha512-/SXVuVnuU5b4dq8OFY4izG+dmGla185PcoqgK6+AJMpmOeY1QYVNbWtCwvSvoAANN5D/wV+EBU8+x7Vf9EphbA==} engines: {node: '>=16'} + '@elastic/transport@9.3.5': + resolution: {integrity: sha512-hIMJbt1guqr3/N2zCN45k9hw9o78qcdsO0xietLe+Bfa+JL0YafHTgkWkM1oT3Ht5sGMJaDcJZiYomSMU6CtTA==} + engines: {node: '>=20'} + '@ember-data/adapter@3.24.0': resolution: {integrity: sha512-vdDvvHF2QyAfLLvJPeREAVUr3M5LsjaZDJJ6Ernf8gJDZBwYvTcYGQEk39GeCzd1Qhw8F7tUTz1DGZXQQRDCrw==} engines: {node: 10.* || >= 12.*} @@ -8785,6 +8793,9 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.21': + resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} + '@swc/types@0.1.26': resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} @@ -9156,6 +9167,9 @@ packages: '@tryghost/elasticsearch@3.0.29': resolution: {integrity: sha512-WGfoGbPXzX7SYPKeD7naYyoF8Lc7KruQPixFSJGLciRQ3wkrZQANQIguD4nA71upp0dhsT4D4aWP296VfJxuPw==} + '@tryghost/elasticsearch@5.1.0': + resolution: {integrity: sha512-LQMrGP2EJ/wM05pU9HoKy8I6YpqHhJVIZ+W4+ytP+G17cSCprDsF4Uf0GFypvxkuFNCtg1eCfDNZCOIuRt12rw==} + '@tryghost/email-mock-receiver@2.1.0': resolution: {integrity: sha512-bgEVM5eRnN53rnqWJ8ZkjuGCzMwwt5Ch29TTXRp3qSGTJruUW44DtN/mjB36DDti0hq4unJV9+D1qIIKxdbong==} @@ -9183,8 +9197,8 @@ packages: '@tryghost/http-cache-utils@0.1.25': resolution: {integrity: sha512-OJAD1ESvV+F81w6IOTKCX0JLGIiJqNn87HVFMskpUC3IUGpqcDP1pCM79d72khxLWy5oRQDYu8xIqicHJ9ST1Q==} - '@tryghost/http-stream@0.1.42': - resolution: {integrity: sha512-wQBwTMLaMAFmKwi0nQeRPcnRvQYFtgYp9d4dJx0b1IkHLmhRwoRGcOkxYd6ajSEGfv7w0GkaaQH4Qd2s/pzuzw==} + '@tryghost/http-stream@2.1.0': + resolution: {integrity: sha512-X23dCRDpDwFBReCYG3WT+F73bg7qMo1tON6nlOiG+6tBgATOAR2EZ7nwT7H0CWzcNUdNxTRGg/zSj7TUHtJQpQ==} '@tryghost/image-transform@1.4.13': resolution: {integrity: sha512-HkIXzNkNVPteAsucNcCpdLtsQr7332RufOgFyubb1hRkrj90swt5QWmAYCJX/HN1cb7mZr+xImqPwYNmsQ18jA==} @@ -9254,8 +9268,8 @@ packages: '@tryghost/limit-service@1.5.2': resolution: {integrity: sha512-BTSnMpVMRauSkxi0yL/V2qVy9nRlAL9maALFAe5MDNTSCvg4zjV2b2S4o6+tgCyGDRkMPeePfrgSuH2YTTbD5Q==} - '@tryghost/logging@2.5.5': - resolution: {integrity: sha512-OILuHgDuoO5Eq1aPrwAmxSPWPRF8AZI+EfyGusZVY70kdBH3XcxNySGZAZIvc0rAtXLOgQPSdQpxPfKbbBxPAg==} + '@tryghost/logging@4.1.0': + resolution: {integrity: sha512-M8tNHRDSGLtAydgNbGRsN4zgUZpprOgXalEcK1112TODmEQMdIUxCjf1Eys3AhOyZZAeQyvonyuqJqDgJDrmww==} '@tryghost/members-csv@2.0.5': resolution: {integrity: sha512-5BMzhWSoJg+pOQuXdO8ygHDxLFcwZLob903NX7lprWQtooLPD8FAWwdFql/ZA7gPG5Bq9lV7TyUFK3hWVtvIlw==} @@ -9296,6 +9310,9 @@ packages: '@tryghost/pretty-stream@0.2.5': resolution: {integrity: sha512-2bc5mk+7BAOt/mrcbjTsmUQhTKqysbIDOUUThSemPVbnij6487DhaRbnYWZW2IGKfAucrEO3h6TTm3kfioKJSw==} + '@tryghost/pretty-stream@2.1.0': + resolution: {integrity: sha512-4ec60PZRCt7CVG1mOYDLiZcZAdIIow1YC6a1fo7e6Dh+ebv8t7uZqnJm3pW3CuMAKMfKZZK8gyQza205k0js2A==} + '@tryghost/prometheus-metrics@1.0.8': resolution: {integrity: sha512-E1LRRwLgiWIN/P20uHgpTK+JVYYQ3+BarKCBszB6qmK5IBANJvQHI3LQV0wDWefVwFyl5FI6AqxLpOOpquvahA==} @@ -9315,8 +9332,8 @@ packages: '@tryghost/request@1.0.12': resolution: {integrity: sha512-vSsyaQ06ySTkDF78JALsLMfOGTFWfRCye3s1zys3sUZy2A51YsmP7kcg5wCemgTTDnRhErOUBotUuECIyg0TGA==} - '@tryghost/request@1.0.17': - resolution: {integrity: sha512-YbxolgTuIORAUUSjfQWJfG1U2sSlZMhXjl+M7/NLvIgCc+Uzx8XjpIrYW6NDAn+lTt9/WAx/36EWr3nzVnuTHg==} + '@tryghost/request@3.1.0': + resolution: {integrity: sha512-aDkQPsZs6W7E/F6aNoc9LUtRfR2xUAja17FRmu1W7AlgdTngEsJant4O3Yh9pqGdrJAv0AFUWIY5zcAmp1Fj6Q==} '@tryghost/root-utils@0.3.38': resolution: {integrity: sha512-ARn8wC6qv867lCr7BZ+IS8S/88K5go0j6HgQFhP27rKje4b40PsxH/P3rO4Ez2NzF/Do8ywFrTWHoLCSsCazXQ==} @@ -9357,6 +9374,9 @@ packages: '@tryghost/validator@0.2.22': resolution: {integrity: sha512-dmobNVEKXMi3K4OdAXLBFwa78hVgy8cYvCPJgfV4h3NtYIyRHqwARr3upT3/ASpVBpKGVBu7XexfSv+tibzsuQ==} + '@tryghost/validator@2.1.0': + resolution: {integrity: sha512-HdM5ucxn91WG4pkLOIojPRFKWW1wF4IS9lhyxv95gA32AMWnGBpA5B1cQ9/Jv30Xn3ZBz6L7nxmoKtmdsRus7A==} + '@tryghost/version@0.1.38': resolution: {integrity: sha512-kVaM7eijFRrBLZLDbmiFxHQ/shmAg5UQ9yM4s9GZN4M3OWY34UHNmjoSOniOHnA5E8fYNA8A3IFmS3FW910xYg==} @@ -9445,6 +9465,12 @@ packages: '@types/color@4.2.1': resolution: {integrity: sha512-ResWeDLy1vozIMbD6JLRKuNBbIcIlBkjTIxVHHd5Cqtm77T+ahH3BWE/PWv1OhFd1HAwcn8no4ig2uTaRXpYQQ==} + '@types/command-line-args@5.2.3': + resolution: {integrity: sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==} + + '@types/command-line-usage@5.0.4': + resolution: {integrity: sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==} + '@types/common-tags@1.8.4': resolution: {integrity: sha512-S+1hLDJPjWNDhcGxsxEbepzaxWqURP/o+3cP4aa2w7yBXgdcmKGQtZzP8JbyfOd0m+33nh+8+kvxYE2UJtBDkg==} @@ -9628,6 +9654,9 @@ packages: '@types/node@22.19.17': resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + '@types/node@24.12.2': + resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + '@types/node@25.6.0': resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} @@ -10434,6 +10463,10 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + apache-arrow@21.1.0: + resolution: {integrity: sha512-kQrYLxhC+NTVVZ4CCzGF6L/uPVOzJmD1T3XgbiUnP7oTeVFOFgEUu6IKNwCDkpFoBVqDKQivlX4RUFqqnWFlEA==} + hasBin: true + app-root-dir@1.0.2: resolution: {integrity: sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==} @@ -10509,6 +10542,10 @@ packages: resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} engines: {node: '>=0.10.0'} + array-back@6.2.3: + resolution: {integrity: sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==} + engines: {node: '>=12.17'} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -11724,6 +11761,10 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk-template@0.4.0: + resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} + engines: {node: '>=12'} + chalk@1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} @@ -12079,6 +12120,19 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + command-line-args@6.0.2: + resolution: {integrity: sha512-AIjYVxrV9X752LmPDLbVYv8aMCuHPSLZJXEo2qo/xJfv+NYhaZ4sMSF01rM+gHPaMgvPM0l5D/F+Qx+i2WfSmQ==} + engines: {node: '>=12.20'} + peerDependencies: + '@75lb/nature': latest + peerDependenciesMeta: + '@75lb/nature': + optional: true + + command-line-usage@7.0.4: + resolution: {integrity: sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg==} + engines: {node: '>=12.20.0'} + commander@0.6.1: resolution: {integrity: sha512-0fLycpl1UMTGX257hRsu/arL/cUbcvQM4zMKwvLvzXtfdezIV4yotPS2dYtknF+NmEfWSoCEF6+hj9XLm/6hEw==} engines: {node: '>= 0.4.x'} @@ -14797,6 +14851,15 @@ packages: find-index@1.1.1: resolution: {integrity: sha512-XYKutXMrIK99YMUPf91KX5QVJoG31/OsgftD6YoTPAObfQIxM4ziA9f0J1AsqKhJmo+IeaIPP0CFopTD4bdUBw==} + find-replace@5.0.2: + resolution: {integrity: sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==} + engines: {node: '>=14'} + peerDependencies: + '@75lb/nature': latest + peerDependenciesMeta: + '@75lb/nature': + optional: true + find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} @@ -14872,6 +14935,9 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true + flatbuffers@25.9.23: + resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==} + flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} @@ -16712,6 +16778,10 @@ packages: engines: {node: '>=6'} hasBin: true + json-bignum@0.0.3: + resolution: {integrity: sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==} + engines: {node: '>=0.8'} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -20813,6 +20883,9 @@ packages: secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + secure-keys@1.0.0: resolution: {integrity: sha512-nZi59hW3Sl5P3+wOO89eHBAAGwmCPd2aE1+dLZV5MO+ItQctIvAqihzaAXIQhvtH4KJPxM080HsnqltR2y8cWg==} @@ -21634,6 +21707,10 @@ packages: tabbable@5.3.3: resolution: {integrity: sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==} + table-layout@4.1.1: + resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} + engines: {node: '>=12.17'} + table@6.9.0: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} @@ -22218,6 +22295,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + typical@7.3.0: + resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} + engines: {node: '>=12.17'} + ua-parser-js@1.0.41: resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==} hasBin: true @@ -22269,6 +22350,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} @@ -23026,6 +23110,10 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wordwrapjs@5.1.1: + resolution: {integrity: sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==} + engines: {node: '>=12.17'} + worker-farm@1.7.0: resolution: {integrity: sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==} @@ -25164,6 +25252,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@elastic/elasticsearch@9.3.4': + dependencies: + '@elastic/transport': 9.3.5 + apache-arrow: 21.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - '@75lb/nature' + - supports-color + '@elastic/transport@8.4.1': dependencies: debug: 4.4.3(supports-color@5.5.0) @@ -25175,6 +25272,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@elastic/transport@9.3.5': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + debug: 4.4.3(supports-color@5.5.0) + hpagent: 1.2.0 + ms: 2.1.3 + secure-json-parse: 4.1.0 + tslib: 2.8.1 + undici: 7.25.0 + transitivePeerDependencies: + - supports-color + '@ember-data/adapter@3.24.0(@babel/core@7.29.0)': dependencies: '@ember-data/private-build-infra': 3.24.0(@babel/core@7.29.0) @@ -25439,7 +25549,7 @@ snapshots: js-string-escape: 1.0.1 jsdom: 16.7.0 json-stable-stringify: 1.3.0 - lodash: 4.18.1 + lodash: 4.17.23 pkg-up: 2.0.0 resolve: 1.22.11 resolve-package-path: 1.2.7 @@ -25462,7 +25572,7 @@ snapshots: '@embroider/core': 0.29.0 assert-never: 1.4.0 ember-cli-babel: 7.26.11 - lodash: 4.18.1 + lodash: 4.17.23 resolve: 1.22.11 semver: 7.7.4 transitivePeerDependencies: @@ -25476,7 +25586,7 @@ snapshots: '@embroider/shared-internals': 0.41.0 assert-never: 1.4.0 ember-cli-babel: 7.26.11 - lodash: 4.18.1 + lodash: 4.17.23 resolve: 1.22.11 semver: 7.7.4 transitivePeerDependencies: @@ -25502,7 +25612,7 @@ snapshots: babel-import-util: 3.0.1 ember-cli-babel: 7.26.11 find-up: 5.0.0 - lodash: 4.18.1 + lodash: 4.17.23 resolve: 1.22.11 semver: 7.7.4 transitivePeerDependencies: @@ -25512,7 +25622,7 @@ snapshots: dependencies: ember-rfc176-data: 0.3.18 fs-extra: 7.0.1 - lodash: 4.18.1 + lodash: 4.17.23 pkg-up: 3.1.0 resolve-package-path: 1.2.7 semver: 7.7.4 @@ -25524,7 +25634,7 @@ snapshots: ember-rfc176-data: 0.3.18 fs-extra: 9.1.0 js-string-escape: 1.0.1 - lodash: 4.18.1 + lodash: 4.17.23 resolve-package-path: 4.0.3 semver: 7.7.4 typescript-memoize: 1.1.1 @@ -25571,7 +25681,7 @@ snapshots: fs-extra: 9.1.0 is-subdir: 1.2.0 js-string-escape: 1.0.1 - lodash: 4.18.1 + lodash: 4.17.23 minimatch: 3.1.5 pkg-entry-points: 1.1.1 resolve-package-path: 4.0.3 @@ -26538,7 +26648,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3))': + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(node-notifier@10.0.1) @@ -26552,7 +26662,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -26576,7 +26686,7 @@ snapshots: - ts-node optional: true - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))': + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(node-notifier@10.0.1) @@ -26590,7 +26700,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -28498,14 +28608,14 @@ snapshots: '@sentry/types': 7.120.4 '@sentry/utils': 7.120.4 - '@sentry/ember@7.120.3(webpack@5.105.4(@swc/core@1.15.21))': + '@sentry/ember@7.120.3(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)))': dependencies: '@embroider/macros': 1.16.13 '@sentry/browser': 7.120.3 '@sentry/core': 7.120.3 '@sentry/types': 7.120.3 '@sentry/utils': 7.120.3 - ember-auto-import: 2.10.0(webpack@5.105.4(@swc/core@1.15.21)) + ember-auto-import: 2.10.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) ember-cli-babel: 7.26.11 ember-cli-htmlbars: 6.3.0 ember-cli-typescript: 5.3.0 @@ -30076,10 +30186,10 @@ snapshots: storybook: 8.6.15(prettier@2.8.8) ts-dedent: 2.2.0 - '@storybook/addon-docs@10.3.3(@types/react@18.3.28)(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4))': + '@storybook/addon-docs@10.3.3(@types/react@18.3.28)(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4))': dependencies: '@mdx-js/react': 3.1.1(@types/react@18.3.28)(react@18.3.1) - '@storybook/csf-plugin': 10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4)) + '@storybook/csf-plugin': 10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)) '@storybook/icons': 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/react-dom-shim': 10.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) react: 18.3.1 @@ -30163,7 +30273,7 @@ snapshots: storybook: 8.6.15(prettier@2.8.8) ts-dedent: 2.2.0 - '@storybook/addon-styling@1.3.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(encoding@0.1.13)(less@4.6.4)(postcss@8.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12))': + '@storybook/addon-styling@1.3.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(encoding@0.1.13)(less@4.6.4)(postcss@8.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12))': dependencies: '@babel/template': 7.28.6 '@babel/types': 7.29.0 @@ -30176,19 +30286,19 @@ snapshots: '@storybook/preview-api': 7.6.24 '@storybook/theming': 7.6.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/types': 7.6.24 - css-loader: 6.11.0(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)) - less-loader: 11.1.4(less@4.6.4)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)) - postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)) + css-loader: 6.11.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)) + less-loader: 11.1.4(less@4.6.4)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)) + postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)) prettier: 2.8.8 resolve-url-loader: 5.0.0 - sass-loader: 13.3.3(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)) - style-loader: 3.3.4(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)) + sass-loader: 13.3.3(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)) + style-loader: 3.3.4(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)) optionalDependencies: less: 4.6.4 postcss: 8.5.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - webpack: 5.105.4(@swc/core@1.15.21)(esbuild@0.25.12) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12) transitivePeerDependencies: - '@rspack/core' - '@types/react' @@ -30201,7 +30311,7 @@ snapshots: - supports-color - typescript - '@storybook/addon-styling@1.3.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(encoding@0.1.13)(less@4.6.4)(postcss@8.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21))': + '@storybook/addon-styling@1.3.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(encoding@0.1.13)(less@4.6.4)(postcss@8.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)))': dependencies: '@babel/template': 7.28.6 '@babel/types': 7.29.0 @@ -30214,19 +30324,19 @@ snapshots: '@storybook/preview-api': 7.6.24 '@storybook/theming': 7.6.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/types': 7.6.24 - css-loader: 6.11.0(webpack@5.105.4(@swc/core@1.15.21)) - less-loader: 11.1.4(less@4.6.4)(webpack@5.105.4(@swc/core@1.15.21)) - postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21)) + css-loader: 6.11.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) + less-loader: 11.1.4(less@4.6.4)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) + postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) prettier: 2.8.8 resolve-url-loader: 5.0.0 - sass-loader: 13.3.3(webpack@5.105.4(@swc/core@1.15.21)) - style-loader: 3.3.4(webpack@5.105.4(@swc/core@1.15.21)) + sass-loader: 13.3.3(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) + style-loader: 3.3.4(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) optionalDependencies: less: 4.6.4 postcss: 8.5.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) transitivePeerDependencies: - '@rspack/core' - '@types/react' @@ -30265,9 +30375,9 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-vite@10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4))': + '@storybook/builder-vite@10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4))': dependencies: - '@storybook/csf-plugin': 10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4)) + '@storybook/csf-plugin': 10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)) storybook: 10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ts-dedent: 2.2.0 vite: 5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1) @@ -30390,7 +30500,7 @@ snapshots: - supports-color - utf-8-validate - '@storybook/csf-plugin@10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4))': + '@storybook/csf-plugin@10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4))': dependencies: storybook: 10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) unplugin: 2.3.11 @@ -30398,7 +30508,7 @@ snapshots: esbuild: 0.27.4 rollup: 4.60.0 vite: 5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1) - webpack: 5.105.4(@swc/core@1.15.21)(esbuild@0.27.4) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4) '@storybook/csf-plugin@8.6.14(storybook@8.6.15(prettier@2.8.8))': dependencies: @@ -30438,7 +30548,7 @@ snapshots: '@storybook/theming': 7.6.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/types': 7.6.17 dequal: 2.0.3 - lodash: 4.18.1 + lodash: 4.17.23 memoizerific: 1.11.3 store2: 2.14.4 telejson: 7.2.0 @@ -30458,7 +30568,7 @@ snapshots: '@storybook/theming': 7.6.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/types': 7.6.24 dequal: 2.0.3 - lodash: 4.18.1 + lodash: 4.17.23 memoizerific: 1.11.3 store2: 2.14.4 telejson: 7.2.0 @@ -30483,7 +30593,7 @@ snapshots: '@storybook/types': 7.6.24 '@types/qs': 6.15.0 dequal: 2.0.3 - lodash: 4.18.1 + lodash: 4.17.23 memoizerific: 1.11.3 qs: 6.15.0 synchronous-promise: 2.0.17 @@ -30506,11 +30616,11 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.6.15(prettier@2.8.8) - '@storybook/react-vite@10.3.3(esbuild@0.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4))': + '@storybook/react-vite@10.3.3(esbuild@0.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)) '@rollup/pluginutils': 5.3.0(rollup@4.60.0) - '@storybook/builder-vite': 10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4)) + '@storybook/builder-vite': 10.3.3(esbuild@0.27.4)(rollup@4.60.0)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.21(@types/node@22.19.17)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)) '@storybook/react': 10.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@2.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -30756,7 +30866,7 @@ snapshots: '@swc/core-win32-x64-msvc@1.15.21': optional: true - '@swc/core@1.15.21': + '@swc/core@1.15.21(@swc/helpers@0.5.21)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.26 @@ -30773,9 +30883,14 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.15.21 '@swc/core-win32-ia32-msvc': 1.15.21 '@swc/core-win32-x64-msvc': 1.15.21 + '@swc/helpers': 0.5.21 '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.21': + dependencies: + tslib: 2.8.1 + '@swc/types@0.1.26': dependencies: '@swc/counter': 0.1.3 @@ -30953,7 +31068,7 @@ snapshots: chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 - lodash: 4.18.1 + lodash: 4.17.23 redent: 3.0.0 '@testing-library/jest-dom@6.9.1': @@ -31250,6 +31365,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@tryghost/elasticsearch@5.1.0': + dependencies: + '@elastic/elasticsearch': 9.3.4 + '@tryghost/debug': 2.1.0 + split2: 4.2.0 + transitivePeerDependencies: + - '@75lb/nature' + - supports-color + '@tryghost/email-mock-receiver@2.1.0': {} '@tryghost/ember-promise-modals@2.0.1(ember-source@3.24.0(@babel/core@7.29.0))(postcss@8.5.6)': @@ -31311,10 +31435,10 @@ snapshots: '@tryghost/http-cache-utils@0.1.25': {} - '@tryghost/http-stream@0.1.42': + '@tryghost/http-stream@2.1.0': dependencies: '@tryghost/errors': 1.3.13 - '@tryghost/request': 1.0.17 + '@tryghost/request': 3.1.0 transitivePeerDependencies: - supports-color @@ -31340,13 +31464,14 @@ snapshots: dependencies: '@breejs/later': 4.2.0 '@tryghost/errors': 1.3.13 - '@tryghost/logging': 2.5.5 + '@tryghost/logging': 4.1.0 bree: 6.5.0 cron-validate: 1.4.5 fastq: 1.20.1 p-wait-for: 3.2.0 workerpool: 9.3.4 transitivePeerDependencies: + - '@75lb/nature' - supports-color '@tryghost/kg-card-factory@5.2.0': {} @@ -31480,20 +31605,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@tryghost/logging@2.5.5': + '@tryghost/logging@4.1.0': dependencies: '@tryghost/bunyan-rotating-filestream': 0.0.7 - '@tryghost/elasticsearch': 3.0.29 - '@tryghost/http-stream': 0.1.42 - '@tryghost/pretty-stream': 0.2.5 - '@tryghost/root-utils': 0.3.38 + '@tryghost/elasticsearch': 5.1.0 + '@tryghost/http-stream': 2.1.0 + '@tryghost/pretty-stream': 2.1.0 + '@tryghost/root-utils': 2.1.0 bunyan: 1.8.15 bunyan-loggly: 2.0.1 - fs-extra: 11.3.0 + fs-extra: 11.3.4 gelf-stream: 1.1.1 json-stringify-safe: 5.0.1 - lodash: 4.17.21 + lodash: 4.18.1 transitivePeerDependencies: + - '@75lb/nature' - supports-color '@tryghost/members-csv@2.0.5': @@ -31521,7 +31647,7 @@ snapshots: '@tryghost/mongo-knex@0.9.4': dependencies: debug: 4.4.3(supports-color@5.5.0) - lodash: 4.18.1 + lodash: 4.17.23 transitivePeerDependencies: - supports-color @@ -31634,16 +31760,23 @@ snapshots: '@tryghost/pretty-stream@0.2.5': dependencies: date-format: 4.0.14 - lodash: 4.17.21 + lodash: 4.17.23 + prettyjson: 1.2.5 + + '@tryghost/pretty-stream@2.1.0': + dependencies: + date-format: 4.0.14 + lodash: 4.18.1 prettyjson: 1.2.5 '@tryghost/prometheus-metrics@1.0.8': dependencies: - '@tryghost/logging': 2.5.5 + '@tryghost/logging': 4.1.0 express: 4.22.1 prom-client: 15.1.3 stoppable: 1.1.0 transitivePeerDependencies: + - '@75lb/nature' - supports-color '@tryghost/promise@0.3.20': {} @@ -31665,14 +31798,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@tryghost/request@1.0.17': + '@tryghost/request@3.1.0': dependencies: '@tryghost/errors': 1.3.13 - '@tryghost/validator': 0.2.22 - '@tryghost/version': 0.1.38 + '@tryghost/validator': 2.1.0 + '@tryghost/version': 2.1.0 cacheable-lookup: 7.0.0 got: 14.6.6 - lodash: 4.17.21 + lodash: 4.18.1 transitivePeerDependencies: - supports-color @@ -31694,8 +31827,9 @@ snapshots: '@tryghost/server@2.0.3': dependencies: '@tryghost/debug': 2.1.0 - '@tryghost/logging': 2.5.5 + '@tryghost/logging': 4.1.0 transitivePeerDependencies: + - '@75lb/nature' - supports-color '@tryghost/social-urls@0.1.60': {} @@ -31746,6 +31880,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@tryghost/validator@2.1.0': + dependencies: + '@tryghost/errors': 1.3.13 + '@tryghost/tpl': 2.1.0 + lodash: 4.18.1 + moment-timezone: 0.5.45 + validator: 7.2.0 + transitivePeerDependencies: + - supports-color + '@tryghost/version@0.1.38': dependencies: '@tryghost/root-utils': 0.3.38 @@ -31880,6 +32024,10 @@ snapshots: dependencies: '@types/color-convert': 2.0.4 + '@types/command-line-args@5.2.3': {} + + '@types/command-line-usage@5.0.4': {} + '@types/common-tags@1.8.4': {} '@types/connect@3.4.38': @@ -32084,6 +32232,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@24.12.2': + dependencies: + undici-types: 7.16.0 + '@types/node@25.6.0': dependencies: undici-types: 7.19.2 @@ -32543,10 +32695,10 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react-swc@4.1.0(vite@7.1.12(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitejs/plugin-react-swc@4.1.0(@swc/helpers@0.5.21)(vite@7.1.12(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.35 - '@swc/core': 1.15.21 + '@swc/core': 1.15.21(@swc/helpers@0.5.21) vite: 7.1.12(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@swc/helpers' @@ -33205,6 +33357,20 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.2 + apache-arrow@21.1.0: + dependencies: + '@swc/helpers': 0.5.21 + '@types/command-line-args': 5.2.3 + '@types/command-line-usage': 5.0.4 + '@types/node': 24.12.2 + command-line-args: 6.0.2 + command-line-usage: 7.0.4 + flatbuffers: 25.9.23 + json-bignum: 0.0.3 + tslib: 2.8.1 + transitivePeerDependencies: + - '@75lb/nature' + app-root-dir@1.0.2: {} app-root-path@2.2.1: {} @@ -33289,6 +33455,8 @@ snapshots: arr-union@3.1.0: {} + array-back@6.2.3: {} + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -33453,7 +33621,7 @@ snapshots: async@2.6.4: dependencies: - lodash: 4.18.1 + lodash: 4.17.23 async@3.2.3: {} @@ -33538,7 +33706,7 @@ snapshots: convert-source-map: 1.9.0 debug: 2.6.9 json5: 0.5.1 - lodash: 4.18.1 + lodash: 4.17.23 minimatch: 3.1.5 path-is-absolute: 1.0.1 private: 0.1.8 @@ -33687,14 +33855,14 @@ snapshots: schema-utils: 2.7.1 webpack: 4.47.0 - babel-loader@8.4.1(@babel/core@7.29.0)(webpack@5.105.4(@swc/core@1.15.21)): + babel-loader@8.4.1(@babel/core@7.29.0)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: '@babel/core': 7.29.0 find-cache-dir: 3.3.2 loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) babel-messages@6.23.0: dependencies: @@ -33734,7 +33902,7 @@ snapshots: babel-plugin-filter-imports@4.0.0: dependencies: '@babel/types': 7.29.0 - lodash: 4.18.1 + lodash: 4.17.23 babel-plugin-htmlbars-inline-precompile@1.0.0: {} @@ -34122,7 +34290,7 @@ snapshots: babel-traverse: 6.26.0 babel-types: 6.26.0 babylon: 6.18.0 - lodash: 4.18.1 + lodash: 4.17.23 transitivePeerDependencies: - supports-color @@ -34136,7 +34304,7 @@ snapshots: debug: 2.6.9 globals: 9.18.0 invariant: 2.2.4 - lodash: 4.18.1 + lodash: 4.17.23 transitivePeerDependencies: - supports-color @@ -34144,7 +34312,7 @@ snapshots: dependencies: babel-runtime: 6.26.0 esutils: 2.0.3 - lodash: 4.18.1 + lodash: 4.17.23 to-fast-properties: 1.0.3 babel6-plugin-strip-class-callcheck@6.0.0: {} @@ -35357,6 +35525,10 @@ snapshots: chai@6.2.2: {} + chalk-template@0.4.0: + dependencies: + chalk: 4.1.2 + chalk@1.1.3: dependencies: ansi-styles: 2.2.1 @@ -35747,6 +35919,20 @@ snapshots: dependencies: delayed-stream: 1.0.0 + command-line-args@6.0.2: + dependencies: + array-back: 6.2.3 + find-replace: 5.0.2 + lodash.camelcase: 4.3.0 + typical: 7.3.0 + + command-line-usage@7.0.4: + dependencies: + array-back: 6.2.3 + chalk-template: 0.4.0 + table-layout: 4.1.1 + typical: 7.3.0 + commander@0.6.1: {} commander@10.0.1: {} @@ -36045,13 +36231,13 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.12 - create-jest@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)): + create-jest@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -36061,13 +36247,13 @@ snapshots: - ts-node optional: true - create-jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)): + create-jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -36155,7 +36341,7 @@ snapshots: dependencies: base64-arraybuffer: 0.2.0 - css-loader@5.2.7(webpack@5.105.4(@swc/core@1.15.21)): + css-loader@5.2.7(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: icss-utils: 5.1.0(postcss@8.5.6) loader-utils: 2.0.4 @@ -36167,9 +36353,9 @@ snapshots: postcss-value-parser: 4.2.0 schema-utils: 3.3.0 semver: 7.7.4 - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) - css-loader@6.11.0(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)): + css-loader@6.11.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -36180,9 +36366,9 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.4 optionalDependencies: - webpack: 5.105.4(@swc/core@1.15.21)(esbuild@0.25.12) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12) - css-loader@6.11.0(webpack@5.105.4(@swc/core@1.15.21)): + css-loader@6.11.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -36193,7 +36379,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.4 optionalDependencies: - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) css-prefers-color-scheme@6.0.3(postcss@8.5.6): dependencies: @@ -37016,7 +37202,7 @@ snapshots: fs-tree-diff: 2.0.1 handlebars: 4.7.9 js-string-escape: 1.0.1 - lodash: 4.18.1 + lodash: 4.17.23 mkdirp: 0.5.6 resolve-package-path: 3.1.0 rimraf: 2.7.1 @@ -37030,7 +37216,7 @@ snapshots: - webpack-cli - webpack-command - ember-auto-import@2.10.0(webpack@5.105.4(@swc/core@1.15.21)): + ember-auto-import@2.10.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: '@babel/core': 7.29.0 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.0) @@ -37040,7 +37226,7 @@ snapshots: '@babel/preset-env': 7.29.2(@babel/core@7.29.0) '@embroider/macros': 1.16.13 '@embroider/shared-internals': 2.9.2 - babel-loader: 8.4.1(@babel/core@7.29.0)(webpack@5.105.4(@swc/core@1.15.21)) + babel-loader: 8.4.1(@babel/core@7.29.0)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) babel-plugin-ember-modules-api-polyfill: 3.5.0 babel-plugin-ember-template-compilation: 2.4.1 babel-plugin-htmlbars-inline-precompile: 5.3.1 @@ -37050,7 +37236,7 @@ snapshots: broccoli-merge-trees: 4.2.0 broccoli-plugin: 4.0.7 broccoli-source: 3.0.1 - css-loader: 5.2.7(webpack@5.105.4(@swc/core@1.15.21)) + css-loader: 5.2.7(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) debug: 4.4.3(supports-color@5.5.0) fs-extra: 10.1.0 fs-tree-diff: 2.0.1 @@ -37058,14 +37244,14 @@ snapshots: is-subdir: 1.2.0 js-string-escape: 1.0.1 lodash: 4.17.23 - mini-css-extract-plugin: 2.10.2(webpack@5.105.4(@swc/core@1.15.21)) + mini-css-extract-plugin: 2.10.2(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) minimatch: 3.1.5 parse5: 6.0.1 pkg-entry-points: 1.1.1 resolve: 1.22.11 resolve-package-path: 4.0.3 semver: 7.7.4 - style-loader: 2.0.0(webpack@5.105.4(@swc/core@1.15.21)) + style-loader: 2.0.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) typescript-memoize: 1.1.1 walk-sync: 3.0.0 transitivePeerDependencies: @@ -37953,10 +38139,10 @@ snapshots: transitivePeerDependencies: - supports-color - ember-in-viewport@4.1.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21)): + ember-in-viewport@4.1.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: '@embroider/macros': 1.16.13 - ember-auto-import: 2.10.0(webpack@5.105.4(@swc/core@1.15.21)) + ember-auto-import: 2.10.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) ember-cli-babel: 7.26.11 ember-destroyable-polyfill: 2.0.3(@babel/core@7.29.0) ember-modifier: 4.2.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0)) @@ -37970,12 +38156,12 @@ snapshots: - supports-color - webpack - ember-infinity@2.3.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21)): + ember-infinity@2.3.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: '@ember/render-modifiers': 2.1.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0)) ember-cli-babel: 7.26.11 ember-cli-htmlbars: 5.7.2 - ember-in-viewport: 4.1.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21)) + ember-in-viewport: 4.1.0(@babel/core@7.29.0)(ember-source@3.24.0(@babel/core@7.29.0))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) transitivePeerDependencies: - '@babel/core' - '@glint/template' @@ -38952,7 +39138,7 @@ snapshots: esquery: 1.7.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 - lodash: 4.18.1 + lodash: 4.17.23 pluralize: 8.0.0 read-pkg-up: 7.0.1 regexp-tree: 0.1.27 @@ -39710,6 +39896,8 @@ snapshots: find-index@1.1.1: {} + find-replace@5.0.2: {} + find-root@1.1.0: {} find-up@2.1.0: @@ -39821,6 +40009,8 @@ snapshots: flat@5.0.2: {} + flatbuffers@25.9.23: {} + flatted@3.4.2: {} flatten@1.0.3: {} @@ -40435,7 +40625,7 @@ snapshots: '@tryghost/config': 2.0.3 '@tryghost/debug': 2.0.3 '@tryghost/errors': 1.3.13 - '@tryghost/logging': 2.5.5 + '@tryghost/logging': 4.1.0 '@tryghost/nql': 0.12.10 '@tryghost/pretty-cli': 3.0.3 '@tryghost/server': 2.0.3 @@ -40449,6 +40639,7 @@ snapshots: semver: 7.7.4 validator: 13.12.0 transitivePeerDependencies: + - '@75lb/nature' - '@opentelemetry/exporter-trace-otlp-http' - bare-abort-controller - bare-buffer @@ -40685,7 +40876,7 @@ snapshots: minimist: 1.2.8 selderee: 0.6.0 - html-validate@8.29.0(jest-diff@29.7.0)(jest-snapshot@29.7.0)(jest@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)))(vitest@1.6.1(@types/node@22.19.17)(jsdom@28.1.0(@noble/hashes@1.8.0))(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)): + html-validate@8.29.0(jest-diff@29.7.0)(jest-snapshot@29.7.0)(jest@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)))(vitest@1.6.1(@types/node@22.19.17)(jsdom@28.1.0(@noble/hashes@1.8.0))(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1)): dependencies: '@html-validate/stylish': 4.3.0 '@sidvind/better-ajv-errors': 3.0.1(ajv@8.18.0) @@ -40696,7 +40887,7 @@ snapshots: prompts: 2.4.2 semver: 7.7.4 optionalDependencies: - jest: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)) + jest: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)) jest-diff: 29.7.0 jest-snapshot: 29.7.0 vitest: 1.6.1(@types/node@22.19.17)(jsdom@28.1.0(@noble/hashes@1.8.0))(less@4.6.4)(lightningcss@1.31.1)(terser@5.46.1) @@ -41017,7 +41208,7 @@ snapshots: cli-width: 2.2.1 external-editor: 3.1.0 figures: 2.0.0 - lodash: 4.18.1 + lodash: 4.17.23 mute-stream: 0.0.7 run-async: 2.4.1 rxjs: 6.6.7 @@ -41033,7 +41224,7 @@ snapshots: cli-width: 3.0.0 external-editor: 3.1.0 figures: 3.2.0 - lodash: 4.18.1 + lodash: 4.17.23 mute-stream: 0.0.8 run-async: 2.4.1 rxjs: 6.6.7 @@ -41615,16 +41806,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)): + jest-cli@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)) + create-jest: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -41637,16 +41828,16 @@ snapshots: - ts-node optional: true - jest-cli@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)): + jest-cli@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + create-jest: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -41658,7 +41849,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)): + jest-config@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)): dependencies: '@babel/core': 7.29.0 '@jest/test-sequencer': 29.7.0 @@ -41684,13 +41875,13 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 22.19.17 - ts-node: 10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3) + ts-node: 10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color optional: true - jest-config@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)): + jest-config@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)): dependencies: '@babel/core': 7.29.0 '@jest/test-sequencer': 29.7.0 @@ -41716,13 +41907,13 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 25.6.0 - ts-node: 10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3) + ts-node: 10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color optional: true - jest-config@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)): + jest-config@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)): dependencies: '@babel/core': 7.29.0 '@jest/test-sequencer': 29.7.0 @@ -41748,7 +41939,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 25.6.0 - ts-node: 10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3) + ts-node: 10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -41788,12 +41979,12 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - jest-extended@7.0.0(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3): + jest-extended@7.0.0(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3): dependencies: jest-diff: 30.3.0 typescript: 5.9.3 optionalDependencies: - jest: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + jest: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) jest-get-type@29.6.3: {} @@ -42073,12 +42264,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)): + jest@29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3)) + jest-cli: 29.7.0(@types/node@22.19.17)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3)) optionalDependencies: node-notifier: 10.0.1 transitivePeerDependencies: @@ -42088,12 +42279,12 @@ snapshots: - ts-node optional: true - jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)): + jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + jest-cli: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) optionalDependencies: node-notifier: 10.0.1 transitivePeerDependencies: @@ -42276,6 +42467,8 @@ snapshots: jsesc@3.1.0: {} + json-bignum@0.0.3: {} + json-buffer@3.0.1: {} json-parse-better-errors@1.0.2: {} @@ -42457,7 +42650,7 @@ snapshots: dependencies: '@tryghost/database-info': 0.3.22 '@tryghost/errors': 1.3.13 - '@tryghost/logging': 2.5.5 + '@tryghost/logging': 4.1.0 '@tryghost/promise': 0.3.8 commander: 5.1.0 compare-ver: 2.0.2 @@ -42471,6 +42664,7 @@ snapshots: optionalDependencies: sqlite3: 5.1.7 transitivePeerDependencies: + - '@75lb/nature' - better-sqlite3 - bluebird - mysql @@ -42622,15 +42816,15 @@ snapshots: transitivePeerDependencies: - supports-color - less-loader@11.1.4(less@4.6.4)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)): + less-loader@11.1.4(less@4.6.4)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)): dependencies: less: 4.6.4 - webpack: 5.105.4(@swc/core@1.15.21)(esbuild@0.25.12) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12) - less-loader@11.1.4(less@4.6.4)(webpack@5.105.4(@swc/core@1.15.21)): + less-loader@11.1.4(less@4.6.4)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: less: 4.6.4 - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) less@4.6.4: dependencies: @@ -43623,11 +43817,11 @@ snapshots: mingo@2.5.3: {} - mini-css-extract-plugin@2.10.2(webpack@5.105.4(@swc/core@1.15.21)): + mini-css-extract-plugin@2.10.2(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: schema-utils: 4.3.3 tapable: 2.3.2 - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) mini-queue@0.0.14: dependencies: @@ -44001,7 +44195,7 @@ snapshots: najax@1.0.7: dependencies: jquery-deferred: 0.3.1 - lodash: 4.18.1 + lodash: 4.17.23 qs: 6.15.0 named-placeholders@1.1.6: @@ -44459,7 +44653,7 @@ snapshots: nwsapi@2.2.23: {} - nx@22.0.4(@swc/core@1.15.21): + nx@22.0.4(@swc/core@1.15.21(@swc/helpers@0.5.21)): dependencies: '@napi-rs/wasm-runtime': 0.2.4 '@yarnpkg/lockfile': 1.1.0 @@ -44507,7 +44701,7 @@ snapshots: '@nx/nx-linux-x64-musl': 22.0.4 '@nx/nx-win32-arm64-msvc': 22.0.4 '@nx/nx-win32-x64-msvc': 22.0.4 - '@swc/core': 1.15.21 + '@swc/core': 1.15.21(@swc/helpers@0.5.21) transitivePeerDependencies: - debug @@ -45353,23 +45547,23 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)): + postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)): dependencies: cosmiconfig: 8.3.6(typescript@5.9.3) jiti: 1.21.7 postcss: 8.5.6 semver: 7.7.4 - webpack: 5.105.4(@swc/core@1.15.21)(esbuild@0.25.12) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12) transitivePeerDependencies: - typescript - postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21)): + postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: cosmiconfig: 8.3.6(typescript@5.9.3) jiti: 1.21.7 postcss: 8.5.6 semver: 7.7.4 - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) transitivePeerDependencies: - typescript @@ -47091,15 +47285,15 @@ snapshots: parse-srcset: 1.0.2 postcss: 8.5.6 - sass-loader@13.3.3(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)): + sass-loader@13.3.3(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)): dependencies: neo-async: 2.6.2 - webpack: 5.105.4(@swc/core@1.15.21)(esbuild@0.25.12) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12) - sass-loader@13.3.3(webpack@5.105.4(@swc/core@1.15.21)): + sass-loader@13.3.3(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: neo-async: 2.6.2 - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) sax@1.2.4: {} @@ -47157,6 +47351,8 @@ snapshots: secure-json-parse@2.7.0: {} + secure-json-parse@4.1.0: {} + secure-keys@1.0.0: {} selderee@0.6.0: @@ -48022,19 +48218,19 @@ snapshots: '@tokenizer/token': 0.3.0 peek-readable: 4.1.0 - style-loader@2.0.0(webpack@5.105.4(@swc/core@1.15.21)): + style-loader@2.0.0(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) - style-loader@3.3.4(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)): + style-loader@3.3.4(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)): dependencies: - webpack: 5.105.4(@swc/core@1.15.21)(esbuild@0.25.12) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12) - style-loader@3.3.4(webpack@5.105.4(@swc/core@1.15.21)): + style-loader@3.3.4(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) style-mod@4.1.3: {} @@ -48267,6 +48463,11 @@ snapshots: tabbable@5.3.3: {} + table-layout@4.1.1: + dependencies: + array-back: 6.2.3 + wordwrapjs: 5.1.1 + table@6.9.0: dependencies: ajv: 8.18.0 @@ -48416,38 +48617,38 @@ snapshots: webpack-sources: 1.4.3 worker-farm: 1.7.0 - terser-webpack-plugin@5.4.0(@swc/core@1.15.21)(esbuild@0.25.12)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)): + terser-webpack-plugin@5.4.0(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.1 - webpack: 5.105.4(@swc/core@1.15.21)(esbuild@0.25.12) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12) optionalDependencies: - '@swc/core': 1.15.21 + '@swc/core': 1.15.21(@swc/helpers@0.5.21) esbuild: 0.25.12 - terser-webpack-plugin@5.4.0(@swc/core@1.15.21)(esbuild@0.27.4)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4)): + terser-webpack-plugin@5.4.0(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.1 - webpack: 5.105.4(@swc/core@1.15.21)(esbuild@0.27.4) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4) optionalDependencies: - '@swc/core': 1.15.21 + '@swc/core': 1.15.21(@swc/helpers@0.5.21) esbuild: 0.27.4 optional: true - terser-webpack-plugin@5.4.0(@swc/core@1.15.21)(webpack@5.105.4(@swc/core@1.15.21)): + terser-webpack-plugin@5.4.0(@swc/core@1.15.21(@swc/helpers@0.5.21))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.1 - webpack: 5.105.4(@swc/core@1.15.21) + webpack: 5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)) optionalDependencies: - '@swc/core': 1.15.21 + '@swc/core': 1.15.21(@swc/helpers@0.5.21) terser@4.8.1: dependencies: @@ -48815,12 +49016,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.9 - jest: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) + jest: 29.7.0(@types/node@25.6.0)(babel-plugin-macros@3.1.0)(node-notifier@10.0.1)(ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -48835,7 +49036,7 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.29.0) jest-util: 30.3.0 - ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.17)(typescript@5.9.3): + ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@22.19.17)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 @@ -48853,10 +49054,10 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.15.21 + '@swc/core': 1.15.21(@swc/helpers@0.5.21) optional: true - ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3): + ts-node@10.9.2(@swc/core@1.15.21(@swc/helpers@0.5.21))(@types/node@25.6.0)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 @@ -48874,7 +49075,7 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.15.21 + '@swc/core': 1.15.21(@swc/helpers@0.5.21) tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: @@ -49019,6 +49220,8 @@ snapshots: typescript@5.9.3: {} + typical@7.3.0: {} + ua-parser-js@1.0.41: {} uc.micro@1.0.6: {} @@ -49059,6 +49262,8 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.16.0: {} + undici-types@7.19.2: {} undici-types@7.24.7: {} @@ -49943,7 +50148,7 @@ snapshots: transitivePeerDependencies: - supports-color - webpack@5.105.4(@swc/core@1.15.21): + webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -49967,7 +50172,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.2 - terser-webpack-plugin: 5.4.0(@swc/core@1.15.21)(webpack@5.105.4(@swc/core@1.15.21)) + terser-webpack-plugin: 5.4.0(@swc/core@1.15.21(@swc/helpers@0.5.21))(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))) watchpack: 2.5.1 webpack-sources: 3.3.4 transitivePeerDependencies: @@ -49975,7 +50180,7 @@ snapshots: - esbuild - uglify-js - webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12): + webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -49999,7 +50204,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.2 - terser-webpack-plugin: 5.4.0(@swc/core@1.15.21)(esbuild@0.25.12)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.25.12)) + terser-webpack-plugin: 5.4.0(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.25.12)) watchpack: 2.5.1 webpack-sources: 3.3.4 transitivePeerDependencies: @@ -50007,7 +50212,7 @@ snapshots: - esbuild - uglify-js - webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4): + webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -50031,7 +50236,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.2 - terser-webpack-plugin: 5.4.0(@swc/core@1.15.21)(esbuild@0.27.4)(webpack@5.105.4(@swc/core@1.15.21)(esbuild@0.27.4)) + terser-webpack-plugin: 5.4.0(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)(webpack@5.105.4(@swc/core@1.15.21(@swc/helpers@0.5.21))(esbuild@0.27.4)) watchpack: 2.5.1 webpack-sources: 3.3.4 transitivePeerDependencies: @@ -50161,6 +50366,8 @@ snapshots: wordwrap@1.0.0: {} + wordwrapjs@5.1.1: {} + worker-farm@1.7.0: dependencies: errno: 0.1.8 From 06b62ae2f3654328ca623271916096818fd0ef23 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 23 Apr 2026 14:42:42 -0500 Subject: [PATCH 4/4] Started scheduling automation polls with scheduler (#27437) closes https://linear.app/ghost/issue/NY-1191 ref https://github.com/TryGhost/Ghost/pull/27421 ref https://github.com/TryGhost/Ghost/pull/27519 Before, we used `setTimeout` to schedule automation polls (with a TODO). Now, we properly use the scheduler. --- ghost/core/core/boot.js | 17 +-- .../core/server/api/endpoints/automations.js | 23 ++++ ghost/core/core/server/api/endpoints/index.js | 4 + .../welcome-email-automations/index.js | 47 +++++++- .../web/api/endpoints/admin/middleware.js | 1 + .../server/web/api/endpoints/admin/routes.js | 3 + .../__snapshots__/automations.test.js.snap | 74 +++++++++++++ .../test/e2e-api/admin/automations.test.js | 101 ++++++++++++++++++ .../services/member-welcome-emails.test.js | 50 ++++----- .../unit/api/endpoints/automations.test.js | 26 +++++ .../welcome-email-automations/index.test.js | 26 ++++- 11 files changed, 334 insertions(+), 38 deletions(-) create mode 100644 ghost/core/core/server/api/endpoints/automations.js create mode 100644 ghost/core/test/e2e-api/admin/__snapshots__/automations.test.js.snap create mode 100644 ghost/core/test/e2e-api/admin/automations.test.js create mode 100644 ghost/core/test/unit/api/endpoints/automations.test.js diff --git a/ghost/core/core/boot.js b/ghost/core/core/boot.js index b5482a66bad..9f653164cf6 100644 --- a/ghost/core/core/boot.js +++ b/ghost/core/core/boot.js @@ -339,6 +339,8 @@ async function initServices() { const emailAddressService = require('./server/services/email-address'); const statsService = require('./server/services/stats'); const explorePingService = require('./server/services/explore-ping'); + const domainEvents = require('@tryghost/domain-events'); + const WelcomeEmailAutomationsService = require('./server/services/welcome-email-automations'); const { createAdapter: createSchedulerAdapter, @@ -347,6 +349,7 @@ async function initServices() { const urlUtils = require('./shared/url-utils'); // Initialize things that other services depend on first. + const apiUrl = urlUtils.urlFor('api', {type: 'admin'}, true); const schedulerAdapter = createSchedulerAdapter(); const [schedulerIntegration] = await Promise.all([ getSchedulerIntegration(), @@ -372,7 +375,7 @@ async function initServices() { emailAnalytics.init(), webhooks.listen(), postScheduling.init({ - apiUrl: urlUtils.urlFor('api', {type: 'admin'}, true), + apiUrl, adapter: schedulerAdapter, integration: schedulerIntegration }), @@ -385,7 +388,13 @@ async function initServices() { recommendationsService.init(), statsService.init(), explorePingService.init(), - giftService.init() + giftService.init(), + new WelcomeEmailAutomationsService().init({ + domainEvents, + apiUrl, + schedulerAdapter, + schedulerIntegration + }) ]); debug('End: Services'); @@ -431,10 +440,6 @@ async function initBackgroundServices({config}) { const outboxService = require('./server/services/outbox'); outboxService.init(); - const domainEvents = require('@tryghost/domain-events'); - const WelcomeEmailAutomationsService = require('./server/services/welcome-email-automations'); - new WelcomeEmailAutomationsService().init(domainEvents); - debug('End: initBackgroundServices'); } diff --git a/ghost/core/core/server/api/endpoints/automations.js b/ghost/core/core/server/api/endpoints/automations.js new file mode 100644 index 00000000000..033991679ff --- /dev/null +++ b/ghost/core/core/server/api/endpoints/automations.js @@ -0,0 +1,23 @@ +const domainEvents = require('@tryghost/domain-events'); +const StartAutomationsPollEvent = require('../../services/welcome-email-automations/events/start-automations-poll-event'); + +/** @type {import('@tryghost/api-framework').Controller} */ +const controller = { + docName: 'automations', + + poll: { + statusCode: 204, + headers: { + cacheInvalidate: false + }, + permissions: { + docName: 'automations', + method: 'poll' + }, + query() { + domainEvents.dispatch(StartAutomationsPollEvent.create()); + } + } +}; + +module.exports = controller; diff --git a/ghost/core/core/server/api/endpoints/index.js b/ghost/core/core/server/api/endpoints/index.js index 1c6c643fccd..7a0d15b511a 100644 --- a/ghost/core/core/server/api/endpoints/index.js +++ b/ghost/core/core/server/api/endpoints/index.js @@ -8,6 +8,10 @@ const localUtils = require('./utils'); /* eslint-disable max-lines */ module.exports = { + get automations() { + return apiFramework.pipeline(require('./automations'), localUtils); + }, + get authentication() { return apiFramework.pipeline(require('./authentication'), localUtils); }, diff --git a/ghost/core/core/server/services/welcome-email-automations/index.js b/ghost/core/core/server/services/welcome-email-automations/index.js index 31f67878a48..5761ba6111d 100644 --- a/ghost/core/core/server/services/welcome-email-automations/index.js +++ b/ghost/core/core/server/services/welcome-email-automations/index.js @@ -1,18 +1,45 @@ // @ts-check +const urlUtils = require('../../../shared/url-utils'); const {oneAtATime} = require('../../../shared/one-at-a-time'); +const {getSignedAdminToken} = require('../../adapters/scheduling/utils'); const StartAutomationsPollEvent = require('./events/start-automations-poll-event'); const {poll} = require('./poll'); const memberWelcomeEmailService = require('../member-welcome-emails/service'); /** @import DomainEvents from '@tryghost/domain-events' */ +/** + * @internal + * @typedef {object} SchedulerAdapter + * @prop {(job: { + * time: number; + * url: string; + * extra: { + * httpMethod: string; + * }; + * }) => void} schedule + */ + +/** + * @internal + * @typedef {object} SchedulerIntegration + * @prop {Array<{ + * id: string; + * secret: string; + * }>} api_keys + */ + class WelcomeEmailAutomationsService { #initialized = false; /** - * @param {Pick} domainEvents + * @param {object} options + * @param {Pick} options.domainEvents + * @param {string} options.apiUrl + * @param {SchedulerAdapter} options.schedulerAdapter + * @param {SchedulerIntegration} options.schedulerIntegration * @returns {void} */ - init(domainEvents) { + init({domainEvents, apiUrl, schedulerAdapter, schedulerIntegration}) { if (this.#initialized) { return; } @@ -25,8 +52,20 @@ class WelcomeEmailAutomationsService { * @param {Readonly} date */ const enqueuePollAt = (date) => { - // TODO(NY-1191): Use Scheduler instead of `setTimeout`. - setTimeout(enqueuePollNow, date.getTime() - Date.now()); + const signedAdminToken = getSignedAdminToken({ + publishedAt: date.toISOString(), + apiUrl, + integration: schedulerIntegration + }); + const url = new URL(urlUtils.urlJoin(apiUrl, 'automations', 'poll')); + url.searchParams.set('token', signedAdminToken); + schedulerAdapter.schedule({ + time: date.getTime(), + url: url.toString(), + extra: { + httpMethod: 'PUT' + } + }); }; domainEvents.subscribe(StartAutomationsPollEvent, oneAtATime(async () => { diff --git a/ghost/core/core/server/web/api/endpoints/admin/middleware.js b/ghost/core/core/server/web/api/endpoints/admin/middleware.js index 61bb856de94..4d3545fb836 100644 --- a/ghost/core/core/server/web/api/endpoints/admin/middleware.js +++ b/ghost/core/core/server/web/api/endpoints/admin/middleware.js @@ -60,6 +60,7 @@ const tokenPermissionCheck = function tokenPermissionCheck(req, res, next) { tiers: ['GET', 'PUT', 'POST'], offers: ['GET', 'PUT', 'POST'], newsletters: ['GET', 'PUT', 'POST'], + automations: ['PUT'], config: ['GET'], explore: ['GET'], schedules: ['PUT'], diff --git a/ghost/core/core/server/web/api/endpoints/admin/routes.js b/ghost/core/core/server/web/api/endpoints/admin/routes.js index 64c90124ccd..eb5044c1d37 100644 --- a/ghost/core/core/server/web/api/endpoints/admin/routes.js +++ b/ghost/core/core/server/web/api/endpoints/admin/routes.js @@ -182,6 +182,9 @@ module.exports = function apiRoutes() { router.put('/labels/:id', mw.authAdminApi, http(api.labels.edit)); router.delete('/labels/:id', mw.authAdminApi, http(api.labels.destroy)); + // ## Automations + router.put('/automations/poll', mw.authAdminApiWithUrl, http(api.automations.poll)); + // ## Automated Emails router.get('/automated_emails', mw.authAdminApi, http(api.automatedEmails.browse)); router.get('/automated_emails/design', mw.authAdminApi, http(api.automatedEmailDesign.read)); diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/automations.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/automations.test.js.snap new file mode 100644 index 00000000000..c3e686e3ae2 --- /dev/null +++ b/ghost/core/test/e2e-api/admin/__snapshots__/automations.test.js.snap @@ -0,0 +1,74 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`Automations API poll does not poll when request lacks a token 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": "INVALID_JWT", + "context": null, + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Invalid token: No token found in URL", + "property": null, + "type": "UnauthorizedError", + }, + ], +} +`; + +exports[`Automations API poll does not poll when request lacks a token 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "235", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Automations API poll does not poll when request token is invalid 1: [body] 1`] = ` +Object { + "errors": Array [ + Object { + "code": "INVALID_JWT", + "context": null, + "details": null, + "ghostErrorCode": null, + "help": null, + "id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "message": "Invalid token: jwt audience invalid. expected: /\\\\/?admin\\\\/?$/", + "property": null, + "type": "UnauthorizedError", + }, + ], +} +`; + +exports[`Automations API poll does not poll when request token is invalid 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "262", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Automations API poll triggers a poll with a valid scheduler integration token 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin", + "x-powered-by": "Express", +} +`; diff --git a/ghost/core/test/e2e-api/admin/automations.test.js b/ghost/core/test/e2e-api/admin/automations.test.js new file mode 100644 index 00000000000..2868b9dfc75 --- /dev/null +++ b/ghost/core/test/e2e-api/admin/automations.test.js @@ -0,0 +1,101 @@ +const sinon = require('sinon'); +const domainEvents = require('@tryghost/domain-events'); +const models = require('../../../core/server/models'); +const {getSignedAdminToken} = require('../../../core/server/adapters/scheduling/utils'); +const {agentProvider, fixtureManager, matchers, assertions} = require('../../utils/e2e-framework'); +const StartAutomationsPollEvent = require('../../../core/server/services/welcome-email-automations/events/start-automations-poll-event'); + +const {anyContentVersion, anyEtag, anyErrorId} = matchers; +const {cacheInvalidateHeaderNotSet} = assertions; + +describe('Automations API', function () { + let agent; + let schedulerIntegration; + let schedulerToken; + + before(async function () { + agent = await agentProvider.getAdminAPIAgent(); + await fixtureManager.init('integrations', 'api_keys'); + + schedulerIntegration = await models.Integration.findOne( + {slug: 'ghost-scheduler'}, + {withRelated: 'api_keys'} + ); + + schedulerToken = getSignedAdminToken({ + publishedAt: new Date().toISOString(), + apiUrl: '/admin/', + integration: schedulerIntegration.toJSON() + }); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('poll', function () { + /** @type {sinon.SinonStub} */ + let dispatchStub; + + beforeEach(function () { + dispatchStub = sinon.stub(domainEvents, 'dispatch'); + }); + + it('does not poll when request lacks a token', async function () { + await agent + .put('automations/poll/') + .expectStatus(401) + .expect(cacheInvalidateHeaderNotSet()) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag + }) + .matchBodySnapshot({ + errors: [{ + id: anyErrorId, + message: 'Invalid token: No token found in URL' + }] + }); + + sinon.assert.notCalled(dispatchStub); + }); + + it('does not poll when request token is invalid', async function () { + const invalidSchedulerToken = getSignedAdminToken({ + publishedAt: new Date().toISOString(), + apiUrl: '/members/', + integration: schedulerIntegration.toJSON() + }); + + await agent + .put(`automations/poll/?token=${invalidSchedulerToken}`) + .expectStatus(401) + .expect(cacheInvalidateHeaderNotSet()) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag + }) + .matchBodySnapshot({ + errors: [{ + id: anyErrorId + }] + }); + + sinon.assert.notCalled(dispatchStub); + }); + + it('triggers a poll with a valid scheduler integration token', async function () { + await agent + .put(`automations/poll/?token=${schedulerToken}`) + .expectStatus(204) + .expectEmptyBody() + .expect(cacheInvalidateHeaderNotSet()) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag + }); + + sinon.assert.calledOnceWithExactly(dispatchStub, sinon.match.instanceOf(StartAutomationsPollEvent)); + }); + }); +}); diff --git a/ghost/core/test/integration/services/member-welcome-emails.test.js b/ghost/core/test/integration/services/member-welcome-emails.test.js index 0f49594f65d..bbd7d8733ab 100644 --- a/ghost/core/test/integration/services/member-welcome-emails.test.js +++ b/ghost/core/test/integration/services/member-welcome-emails.test.js @@ -130,30 +130,32 @@ describe('Member Welcome Emails Integration', function () { }); it('creates automation run when member source is "member"', async function () { - const before = new Date(Date.now() - 1000); - - const member = await membersService.api.members.create({ - email: 'welcome-test@example.com', - name: 'Welcome Test Member' - }, {}); - - const after = new Date(Date.now() + 1000); - - const runs = await db.knex('welcome_email_automation_runs') - .where('member_id', member.id); - - assert.equal(runs.length, 1); - const run = runs[0]; - assert.equal(run.member_id, member.id); - assert.ok(run.welcome_email_automation_id); - assert.ok(run.next_welcome_email_automated_email_id); - assert.equal(run.step_started_at, null); - assert.equal(run.step_attempts, 0); - assert.equal(run.exit_reason, null); - - const timestamp = parseDatabaseDate(run.ready_at); - assert(timestamp >= before); - assert(timestamp <= after); + await db.knex.transaction(async (trx) => { + const before = new Date(Date.now() - 1000); + + const member = await membersService.api.members.create({ + email: 'welcome-test@example.com', + name: 'Welcome Test Member' + }, {transacting: trx}); + + const after = new Date(Date.now() + 1000); + + const runs = await trx('welcome_email_automation_runs') + .where('member_id', member.id); + + assert.equal(runs.length, 1); + const run = runs[0]; + assert.equal(run.member_id, member.id); + assert.ok(run.welcome_email_automation_id); + assert.ok(run.next_welcome_email_automated_email_id); + assert.equal(run.step_started_at, null); + assert.equal(run.step_attempts, 0); + assert.equal(run.exit_reason, null); + + const timestamp = parseDatabaseDate(run.ready_at); + assert(timestamp >= before); + assert(timestamp <= after); + }); }); it('does NOT create automation run when member is imported', async function () { diff --git a/ghost/core/test/unit/api/endpoints/automations.test.js b/ghost/core/test/unit/api/endpoints/automations.test.js new file mode 100644 index 00000000000..b00138e4fdf --- /dev/null +++ b/ghost/core/test/unit/api/endpoints/automations.test.js @@ -0,0 +1,26 @@ +const assert = require('node:assert/strict'); +const sinon = require('sinon'); +const domainEvents = require('@tryghost/domain-events'); +const automationsController = require('../../../../core/server/api/endpoints/automations'); +const StartAutomationsPollEvent = require('../../../../core/server/services/welcome-email-automations/events/start-automations-poll-event'); + +describe('Automations controller', function () { + let dispatchStub; + + beforeEach(function () { + dispatchStub = sinon.stub(domainEvents, 'dispatch'); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('poll', function () { + it('dispatches a StartAutomationsPollEvent', function () { + const result = automationsController.poll.query({}); + + sinon.assert.calledOnceWithExactly(dispatchStub, sinon.match.instanceOf(StartAutomationsPollEvent)); + assert.equal(result, undefined); + }); + }); +}); diff --git a/ghost/core/test/unit/server/services/welcome-email-automations/index.test.js b/ghost/core/test/unit/server/services/welcome-email-automations/index.test.js index 3a12928142d..993af8d8050 100644 --- a/ghost/core/test/unit/server/services/welcome-email-automations/index.test.js +++ b/ghost/core/test/unit/server/services/welcome-email-automations/index.test.js @@ -6,6 +6,9 @@ const StartAutomationsPollEvent = require('../../../../../core/server/services/w describe('WelcomeEmailAutomationsService', function () { let service; let domainEvents; + let schedulerAdapter; + let schedulerIntegration; + let initOptions; beforeEach(function () { service = new WelcomeEmailAutomationsService(); @@ -13,6 +16,21 @@ describe('WelcomeEmailAutomationsService', function () { dispatch: sinon.stub(), subscribe: sinon.stub() }; + schedulerAdapter = { + schedule: sinon.stub() + }; + schedulerIntegration = { + api_keys: [{ + id: 'fake-key-id', + secret: '00'.repeat(32) + }] + }; + initOptions = { + domainEvents, + apiUrl: 'https://fake.example.com/ghost/api/admin', + schedulerAdapter, + schedulerIntegration + }; }); afterEach(function () { @@ -21,20 +39,20 @@ describe('WelcomeEmailAutomationsService', function () { describe('init', function () { it('dispatches a StartAutomationsPollEvent', function () { - service.init(domainEvents); + service.init(initOptions); sinon.assert.calledWith(domainEvents.dispatch, sinon.match.instanceOf(StartAutomationsPollEvent)); }); it('subscribes to StartAutomationsPollEvent', function () { - service.init(domainEvents); + service.init(initOptions); sinon.assert.calledOnceWithExactly(domainEvents.subscribe, StartAutomationsPollEvent, sinon.match.func); }); it('subscribes only once when init is called multiple times', function () { - service.init(domainEvents); - service.init(domainEvents); + service.init(initOptions); + service.init(initOptions); sinon.assert.calledOnce(domainEvents.subscribe); });