From aff981f13d50be99fe14c066ffbf513a9ed3523b Mon Sep 17 00:00:00 2001 From: Sag Date: Mon, 4 May 2026 16:24:58 +0200 Subject: [PATCH 1/4] Added paid welcome email for gift member signups (#27648) ref https://linear.app/ghost/issue/BER-3541 Send the **paid welcome email** when a new member redeems a gift subscription, and suppress the duplicate paid welcome when that gift member subsequently starts a real paid subscription. Previously, gift members received no welcome email on signup. --- .../member-welcome-emails/constants.js | 6 + .../repositories/member-repository.js | 52 ++++--- .../welcome-email-automations/poll.js | 5 +- .../members/gift-subscriptions.test.js | 138 +++++++++++++++--- .../welcome-email-automations/poll.test.js | 39 +++++ .../repositories/member-repository.test.js | 138 +++++++++++++++--- 6 files changed, 314 insertions(+), 64 deletions(-) diff --git a/ghost/core/core/server/services/member-welcome-emails/constants.js b/ghost/core/core/server/services/member-welcome-emails/constants.js index 5d4d519a13e..b1cde94548d 100644 --- a/ghost/core/core/server/services/member-welcome-emails/constants.js +++ b/ghost/core/core/server/services/member-welcome-emails/constants.js @@ -7,6 +7,11 @@ const MEMBER_WELCOME_EMAIL_SLUGS = { paid: 'member-welcome-email-paid' }; +const MEMBER_WELCOME_EMAIL_ELIGIBLE_STATUSES = { + free: ['free'], + paid: ['paid', 'gift'] +}; + const MEMBER_WELCOME_EMAIL_TAG = 'member-welcome-email'; const MESSAGES = { @@ -23,5 +28,6 @@ module.exports = { MEMBER_WELCOME_EMAIL_LOG_KEY, MEMBER_WELCOME_EMAIL_TAG, MEMBER_WELCOME_EMAIL_SLUGS, + MEMBER_WELCOME_EMAIL_ELIGIBLE_STATUSES, MESSAGES }; diff --git a/ghost/core/core/server/services/members/members-api/repositories/member-repository.js b/ghost/core/core/server/services/members/members-api/repositories/member-repository.js index 4ba2e800af0..9551df91055 100644 --- a/ghost/core/core/server/services/members/members-api/repositories/member-repository.js +++ b/ghost/core/core/server/services/members/members-api/repositories/member-repository.js @@ -382,26 +382,35 @@ module.exports = class MemberRepository { let member; const isFreeSignup = !stripeCustomer && memberData.status === 'free'; - const shouldCheckFreeWelcomeEmail = WELCOME_EMAIL_SOURCES.includes(source) && isFreeSignup; - let isFreeWelcomeEmailActive = false; - let freeWelcomeAutomation = null; - let freeWelcomeEmail = null; - - if (shouldCheckFreeWelcomeEmail && this._WelcomeEmailAutomation) { - freeWelcomeAutomation = await this._WelcomeEmailAutomation.findOne( - {slug: MEMBER_WELCOME_EMAIL_SLUGS.free}, - {...options, withRelated: ['welcomeEmailAutomatedEmail']} - ); - freeWelcomeEmail = freeWelcomeAutomation?.related('welcomeEmailAutomatedEmail'); - isFreeWelcomeEmailActive = Boolean( - freeWelcomeAutomation && - freeWelcomeEmail && - freeWelcomeEmail.get('lexical') && - freeWelcomeAutomation.get('status') === 'active' - ); + const isGiftSignup = !stripeCustomer && memberData.status === 'gift'; + let welcomeEmailToEnqueue = null; + + if (this._WelcomeEmailAutomation && WELCOME_EMAIL_SOURCES.includes(source)) { + const getActiveWelcomeEmailToEnqueue = async (slug) => { + const automation = await this._WelcomeEmailAutomation.findOne( + {slug}, + {...options, withRelated: ['welcomeEmailAutomatedEmail']} + ); + const email = automation?.related('welcomeEmailAutomatedEmail'); + const isActive = Boolean( + automation && + email && + email.get('lexical') && + automation.get('status') === 'active' + ); + + return isActive ? {automation, email} : null; + }; + + if (isFreeSignup) { + welcomeEmailToEnqueue = await getActiveWelcomeEmailToEnqueue(MEMBER_WELCOME_EMAIL_SLUGS.free); + } else if (isGiftSignup) { + // As gift members get access to a paid tier, they receive the paid welcome email + welcomeEmailToEnqueue = await getActiveWelcomeEmailToEnqueue(MEMBER_WELCOME_EMAIL_SLUGS.paid); + } } - if (isFreeWelcomeEmailActive && isFreeSignup) { + if (welcomeEmailToEnqueue) { const runMemberCreation = async (transacting) => { const newMember = await this._Member.add({ ...memberData, @@ -409,9 +418,9 @@ module.exports = class MemberRepository { }, {...memberAddOptions, transacting}); await this._WelcomeEmailAutomationRun.add({ - welcome_email_automation_id: freeWelcomeAutomation.id, + welcome_email_automation_id: welcomeEmailToEnqueue.automation.id, member_id: newMember.id, - next_welcome_email_automated_email_id: freeWelcomeEmail.id, + next_welcome_email_automated_email_id: welcomeEmailToEnqueue.email.id, ready_at: new Date(), step_started_at: null, step_attempts: 0, @@ -1527,7 +1536,8 @@ module.exports = class MemberRepository { // Send paid welcome email if: // 1. The paid welcome email is active // 2. The member status changed to 'paid' - if (updatedMember.get('status') === 'paid' && isPaidWelcomeEmailActive) { + // 3. The previous status wasn't 'gift', as gift members already received the paid welcome email on signup + if (updatedMember.get('status') === 'paid' && updatedMember._previousAttributes.status !== 'gift' && isPaidWelcomeEmailActive) { await this._WelcomeEmailAutomationRun.add({ welcome_email_automation_id: paidWelcomeAutomation.id, member_id: memberModel.id, 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 c31a78a1d0e..5c6ca25d290 100644 --- a/ghost/core/core/server/services/welcome-email-automations/poll.js +++ b/ghost/core/core/server/services/welcome-email-automations/poll.js @@ -1,6 +1,6 @@ const logging = require('@tryghost/logging'); const db = require('../../data/db'); -const {MEMBER_WELCOME_EMAIL_SLUGS} = require('../member-welcome-emails/constants'); +const {MEMBER_WELCOME_EMAIL_SLUGS, MEMBER_WELCOME_EMAIL_ELIGIBLE_STATUSES} = require('../member-welcome-emails/constants'); const {AutomatedEmailRecipient, Member, WelcomeEmailAutomationRun} = require('../../models'); /** @import {Knex} from 'knex' */ @@ -206,7 +206,8 @@ async function processRun({ // TODO(NY-1193): Bail if member is unsubscribed - if (member.get('status') !== memberStatus) { + const eligibleStatuses = MEMBER_WELCOME_EMAIL_ELIGIBLE_STATUSES[memberStatus]; + if (!eligibleStatuses.includes(member.get('status'))) { await markExited(run.id, 'member changed status'); return; } diff --git a/ghost/core/test/e2e-api/members/gift-subscriptions.test.js b/ghost/core/test/e2e-api/members/gift-subscriptions.test.js index 58be268bc27..c62b43163c0 100644 --- a/ghost/core/test/e2e-api/members/gift-subscriptions.test.js +++ b/ghost/core/test/e2e-api/members/gift-subscriptions.test.js @@ -750,26 +750,41 @@ describe('Gift Subscriptions', function () { const existingMember = await models.Member.findOne({email}, {require: false}); assert.equal(existingMember, null, 'Member should not exist before magic link confirmation'); - // Set up an active free welcome email automation so the test would catch - // any regression that re-introduces the spurious automation run for gift members - const emailDesignSetting = await models.EmailDesignSetting.findOne( - {slug: 'default-automated-email'}, - {require: true} - ); - const welcomeEmailAutomation = await models.WelcomeEmailAutomation.add({ - name: 'Free welcome email', - slug: 'member-welcome-email-free', - status: 'active' - }); - await models.WelcomeEmailAutomatedEmail.add({ - welcome_email_automation_id: welcomeEmailAutomation.id, - delay_days: 0, - subject: 'Welcome to the site!', - lexical: JSON.stringify({root: {children: [{type: 'paragraph', children: [{text: 'Welcome'}]}]}}), - email_design_setting_id: emailDesignSetting.id - }); + let freeWelcomeAutomation; + let paidWelcomeAutomation; try { + // Set up both free and paid welcome email automations to verify gift + // redemption picks the paid welcome email (not the free one). + const emailDesignSetting = await models.EmailDesignSetting.findOne( + {slug: 'default-automated-email'}, + {require: true} + ); + freeWelcomeAutomation = await models.WelcomeEmailAutomation.add({ + name: 'Free welcome email', + slug: 'member-welcome-email-free', + status: 'active' + }); + await models.WelcomeEmailAutomatedEmail.add({ + welcome_email_automation_id: freeWelcomeAutomation.id, + delay_days: 0, + subject: 'Welcome to the site!', + lexical: JSON.stringify({root: {children: [{type: 'paragraph', children: [{text: 'Welcome'}]}]}}), + email_design_setting_id: emailDesignSetting.id + }); + paidWelcomeAutomation = await models.WelcomeEmailAutomation.add({ + name: 'Paid welcome email', + slug: 'member-welcome-email-paid', + status: 'active' + }); + await models.WelcomeEmailAutomatedEmail.add({ + welcome_email_automation_id: paidWelcomeAutomation.id, + delay_days: 0, + subject: 'Welcome to the paid tier!', + lexical: JSON.stringify({root: {children: [{type: 'paragraph', children: [{text: 'Welcome paid'}]}]}}), + email_design_setting_id: emailDesignSetting.id + }); + await models.Product.edit({ welcome_page_url: '' }, { @@ -805,11 +820,17 @@ describe('Gift Subscriptions', function () { assert.ok(gift.get('redeemed_at')); assert.ok(gift.get('consumes_at')); - // Verify the free welcome automation did NOT enqueue a run for this gift member + // Verify the paid welcome automation enqueued a run for this gift member + // (and the free welcome automation did NOT — gift redemption is a paid-tier experience) const welcomeRuns = await models.WelcomeEmailAutomationRun.findAll({ filter: `member_id:'${member.id}'` }); - assert.equal(welcomeRuns.length, 0, 'Should not enqueue free welcome email automation for gift member'); + assert.equal(welcomeRuns.length, 1, 'Should enqueue exactly one welcome email automation run for gift member'); + assert.equal( + welcomeRuns.models[0].get('welcome_email_automation_id'), + paidWelcomeAutomation.id, + 'Should enqueue the paid welcome email automation, not the free one' + ); // Verify gift subscription started staff notification was sent, // and that no other unwanted staff notifications were sent (i.e. no "Free member signup" email) @@ -830,7 +851,19 @@ describe('Gift Subscriptions', function () { }, { id: paidProduct.id }); - await models.WelcomeEmailAutomation.destroy({id: welcomeEmailAutomation.id}); + + for (const automation of [freeWelcomeAutomation, paidWelcomeAutomation]) { + if (!automation) { + continue; + } + const runs = await models.WelcomeEmailAutomationRun.findAll({ + filter: `welcome_email_automation_id:'${automation.id}'` + }); + for (const run of runs.models) { + await models.WelcomeEmailAutomationRun.destroy({id: run.id}); + } + await models.WelcomeEmailAutomation.destroy({id: automation.id}); + } } }); }); @@ -1068,6 +1101,69 @@ describe('Gift Subscriptions', function () { assert.ok(giftAfterActivation.get('consumed_at'), 'Gift should have consumed_at set'); }); + it('does not enqueue a second paid welcome email when a gift member upgrades to paid', async function () { + const {member, gift} = await setupGiftMember({cadence: 'month', consumesInDays: 30}); + + let paidWelcomeAutomation; + + try { + const emailDesignSetting = await models.EmailDesignSetting.findOne( + {slug: 'default-automated-email'}, + {require: true} + ); + paidWelcomeAutomation = await models.WelcomeEmailAutomation.add({ + name: 'Paid welcome email', + slug: 'member-welcome-email-paid', + status: 'active' + }); + await models.WelcomeEmailAutomatedEmail.add({ + welcome_email_automation_id: paidWelcomeAutomation.id, + delay_days: 0, + subject: 'Welcome to the paid tier!', + lexical: JSON.stringify({root: {children: [{type: 'paragraph', children: [{text: 'Welcome paid'}]}]}}), + email_design_setting_id: emailDesignSetting.id + }); + + const runsBefore = await models.WelcomeEmailAutomationRun.findAll({ + filter: `member_id:'${member.id}'` + }); + + const customer = stripeMocker.createCustomer({email: member.get('email')}); + const price = await stripeMocker.getPriceForTier('default-product', 'month'); + + await stripeMocker.createSubscription({customer, price}); + await DomainEvents.allSettled(); + + // Gift consumed, member upgraded to paid + const giftAfterActivation = await models.Gift.findOne({token: gift.get('token')}, {require: true}); + assert.equal(giftAfterActivation.get('status'), 'consumed'); + + const memberAfterActivation = await models.Member.findOne({id: member.id}, {require: true}); + assert.equal(memberAfterActivation.get('status'), 'paid'); + + // No new paid welcome run should have been enqueued — the gift redemption + // already onboarded this member with the paid welcome email. + const runsAfter = await models.WelcomeEmailAutomationRun.findAll({ + filter: `member_id:'${member.id}'` + }); + assert.equal( + runsAfter.length, + runsBefore.length, + 'Should not enqueue a second paid welcome email on gift → paid transition' + ); + } finally { + if (paidWelcomeAutomation) { + const runs = await models.WelcomeEmailAutomationRun.findAll({ + filter: `welcome_email_automation_id:'${paidWelcomeAutomation.id}'` + }); + for (const run of runs.models) { + await models.WelcomeEmailAutomationRun.destroy({id: run.id}); + } + await models.WelcomeEmailAutomation.destroy({id: paidWelcomeAutomation.id}); + } + } + }); + it('Returns 401 for unauthenticated members', async function () { await membersAgent.post('/api/create-stripe-checkout-session/') .body({ 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 2caca408e4d..5466a4329ef 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 @@ -496,6 +496,45 @@ describe('welcome email automations poll', function () { assert.equal(updatedRun.step_attempts, 0); }); + it('allows the paid welcome email to be sent to a gift member', async function () { + const automation = await createAutomation({ + slug: MEMBER_WELCOME_EMAIL_SLUGS.paid + }); + const automatedEmail = await createAutomatedEmail({ + welcome_email_automation_id: automation.id + }); + const member = await createMember({ + email: 'gift-member@example.com', + name: 'Gift Member', + status: 'gift' + }); + 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 poll(options); + + sinon.assert.calledWith(options.memberWelcomeEmailService.api.send, sinon.match({ + member: { + name: 'Gift Member', + email: 'gift-member@example.com', + uuid: member.uuid + }, + memberStatus: 'paid' + })); + + const updatedRun = await readRun(run.id); + assert.equal(updatedRun.exit_reason, 'finished'); + assert.equal(updatedRun.next_welcome_email_automated_email_id, null); + + const recipients = await readTrackedRecipients(); + assert.equal(recipients.length, 1); + assert.equal(recipients[0].member_email, 'gift-member@example.com'); + }); + it('fails if run stored with more attempts than now supported', async function () { const automation = await createAutomation(); const automatedEmail = await createAutomatedEmail({ diff --git a/ghost/core/test/unit/server/services/members/members-api/repositories/member-repository.test.js b/ghost/core/test/unit/server/services/members/members-api/repositories/member-repository.test.js index e937762cdaa..edc9be182ae 100644 --- a/ghost/core/test/unit/server/services/members/members-api/repositories/member-repository.test.js +++ b/ghost/core/test/unit/server/services/members/members-api/repositories/member-repository.test.js @@ -1538,7 +1538,7 @@ describe('MemberRepository', function () { process.env.NODE_ENV = oldNodeEnv; }); - it('creates automation run for allowed source', async function () { + it('creates automation run for free member signup (free welcome email)', async function () { const repo = new MemberRepository({ Member, Outbox, @@ -1563,6 +1563,83 @@ describe('MemberRepository', function () { assert.equal(runCall.exit_reason, null); }); + it('creates automation run for gift member signup (paid welcome email)', async function () { + // Override stub with paid welcome email + WelcomeEmailAutomation.findOne = sinon.stub().resolves({ + id: 'automation_id_paid', + get: sinon.stub().callsFake((key) => { + const data = {status: 'active'}; + return data[key]; + }), + related: sinon.stub().callsFake((relation) => { + assert.equal(relation, 'welcomeEmailAutomatedEmail'); + return { + id: 'automated_email_id_paid', + get: sinon.stub().callsFake((key) => { + const data = {lexical: '{"root":{}}'}; + return data[key]; + }) + }; + }) + }); + + const repo = new MemberRepository({ + Member, + Outbox, + WelcomeEmailAutomationRun, + MemberStatusEvent, + MemberSubscribeEventModel: MemberSubscribeEvent, + newslettersService, + WelcomeEmailAutomation, + OfferRedemption: mockOfferRedemption + }); + + await repo.create({email: 'test@example.com', name: 'Test Member', status: 'gift'}, {}); + + sinon.assert.calledOnce(WelcomeEmailAutomation.findOne); + assert.equal(WelcomeEmailAutomation.findOne.firstCall.args[0].slug, 'member-welcome-email-paid'); + + sinon.assert.calledOnce(WelcomeEmailAutomationRun.add); + const runCall = WelcomeEmailAutomationRun.add.firstCall.args[0]; + assert.equal(runCall.welcome_email_automation_id, 'automation_id_paid'); + assert.equal(runCall.member_id, 'member_id_123'); + assert.equal(runCall.next_welcome_email_automated_email_id, 'automated_email_id_paid'); + }); + + it('does NOT create automation run for a gift signup when the paid welcome email is inactive', async function () { + // Override stub with inactive paid welcome email + WelcomeEmailAutomation.findOne = sinon.stub().resolves({ + id: 'automation_id_paid', + get: sinon.stub().callsFake((key) => { + const data = {status: 'inactive'}; + return data[key]; + }), + related: sinon.stub().callsFake(() => ({ + id: 'automated_email_id_paid', + get: sinon.stub().callsFake((key) => { + const data = {lexical: '{"root":{}}'}; + return data[key]; + }) + })) + }); + + const repo = new MemberRepository({ + Member, + Outbox, + WelcomeEmailAutomationRun, + MemberStatusEvent, + MemberSubscribeEventModel: MemberSubscribeEvent, + newslettersService, + WelcomeEmailAutomation, + OfferRedemption: mockOfferRedemption + }); + + await repo.create({email: 'test@example.com', name: 'Test Member', status: 'gift'}, {}); + + sinon.assert.notCalled(WelcomeEmailAutomationRun.add); + sinon.assert.notCalled(Member.transaction); + }); + it('does not create automation run for disallowed sources', async function () { const repo = new MemberRepository({ Member, @@ -1638,6 +1715,7 @@ describe('MemberRepository', function () { sinon.assert.notCalled(WelcomeEmailAutomationRun.add); }); + it('does NOT create automation run when member is signing up for a paid subscription (stripeCustomer is present)', async function () { const StripeCustomer = { upsert: sinon.stub().resolves() @@ -1682,25 +1760,6 @@ describe('MemberRepository', function () { sinon.assert.notCalled(WelcomeEmailAutomation.findOne); sinon.assert.notCalled(Member.transaction); }); - - it('does NOT create automation run when member is created with a non-free status (e.g. gift)', async function () { - const repo = new MemberRepository({ - Member, - Outbox, - WelcomeEmailAutomationRun, - MemberStatusEvent, - MemberSubscribeEventModel: MemberSubscribeEvent, - newslettersService, - WelcomeEmailAutomation, - OfferRedemption: mockOfferRedemption - }); - - await repo.create({email: 'test@example.com', name: 'Test Member', status: 'gift'}, {}); - - sinon.assert.notCalled(WelcomeEmailAutomationRun.add); - sinon.assert.notCalled(WelcomeEmailAutomation.findOne); - sinon.assert.notCalled(Member.transaction); - }); }); describe('linkSubscription - automation run integration', function () { @@ -2014,6 +2073,45 @@ describe('MemberRepository', function () { sinon.assert.notCalled(WelcomeEmailAutomationRun.add); }); + + it('does NOT create automation run when previous status was "gift" (already received paid welcome at redemption)', async function () { + Member.edit.resolves({ + attributes: {status: 'paid'}, + _previousAttributes: {status: 'gift'}, + get: sinon.stub().callsFake((key) => { + const data = {status: 'paid'}; + return data[key]; + }) + }); + + const repo = new MemberRepository({ + Member, + Outbox, + WelcomeEmailAutomationRun, + MemberPaidSubscriptionEvent, + StripeCustomerSubscription, + MemberProductEvent, + MemberStatusEvent, + stripeAPIService, + productRepository, + WelcomeEmailAutomation, + OfferRedemption: mockOfferRedemption + }); + + sinon.stub(repo, 'getSubscriptionByStripeID').resolves(null); + + await repo.linkSubscription({ + id: 'member_id_123', + subscription: subscriptionData + }, { + transacting: { + executionPromise: Promise.resolve() + }, + context: {} + }); + + sinon.assert.notCalled(WelcomeEmailAutomationRun.add); + }); }); describe('create - member status', function () { From e10e3f56877df45d52659b937cc594863f805709 Mon Sep 17 00:00:00 2001 From: Chris Raible Date: Mon, 4 May 2026 12:10:36 -0700 Subject: [PATCH 2/4] Split up email renderer unit test to avoid timeouts (#27661) refs https://github.com/TryGhost/Ghost/actions/runs/25331363477/job/74271567206#step:7:2036 This unit test has been timing out occasionally in CI; this splits this test, which covers 4 locales, into 4 separate test cases, so each case can use the full 2 second timeout. This should prevent it from timing out in CI. --- .../server/services/email-service/email-renderer.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ghost/core/test/unit/server/services/email-service/email-renderer.test.js b/ghost/core/test/unit/server/services/email-service/email-renderer.test.js index c1b2666a001..2a58dd010e2 100644 --- a/ghost/core/test/unit/server/services/email-service/email-renderer.test.js +++ b/ghost/core/test/unit/server/services/email-service/email-renderer.test.js @@ -1404,8 +1404,8 @@ describe('Email renderer', function () { assert.match(response.html, /direction:\s*ltr/); }); - it('Renders RTL attributes for Persian, Arabic, Hebrew, and Urdu', async function () { - for (const locale of ['fa', 'ar', 'he', 'ur']) { + for (const locale of ['fa', 'ar', 'he', 'ur']) { + it(`Renders RTL attributes for ${locale}`, async function () { customSettings.locale = locale; const post = createModel(basePost); const newsletter = createModel(baseNewsletter); @@ -1413,8 +1413,8 @@ describe('Email renderer', function () { assert.match(response.html, new RegExp(``), `expected rtl for ${locale}`); assert.match(response.html, /direction:\s*rtl/, `expected direction: rtl in body for ${locale}`); assert.match(response.html, /class="feedback-buttons-container" dir="rtl"/, `expected feedback buttons dir="rtl" for ${locale}`); - } - }); + }); + } it('preserves multiline code block whitespace in the shared email wrapper', async function () { renderedPost = '
const firstLine = 1;\nconst secondLine = 2;
'; From 0c16fef0e721b747b37ed4f84420d68e00514300 Mon Sep 17 00:00:00 2001 From: Michael Barrett Date: Mon, 4 May 2026 20:33:26 +0100 Subject: [PATCH 3/4] Bucketed `gift` member status alongside `comped` in Tinybird analytics (#27614) ref https://linear.app/ghost/issue/BER-3480 The new `gift` member status was already flowing into Tinybird via the status-agnostic tracker, but the dashboard's "paid" audience filter silently excluded gift sessions. Extended the existing comped expansion in `filtered_sessions` and the `top_pages/top_locations` pipes so that filtering by `paid` now returns `paid + comped + gift`, matching how comped has always been treated. Updated fixtures, mocks, docs, and test assertions accordingly. --- .../core/server/data/tinybird/ARCHITECTURE.md | 4 ++-- .../data/tinybird/endpoints/api_kpis.pipe | 9 ++++++++- .../data/tinybird/endpoints/api_kpis_v2.pipe | 9 ++++++++- .../tinybird/endpoints/api_top_locations.pipe | 2 +- .../endpoints/api_top_locations_v2.pipe | 2 +- .../data/tinybird/endpoints/api_top_pages.pipe | 2 +- .../tinybird/endpoints/api_top_pages_v2.pipe | 2 +- .../tinybird/endpoints/api_top_pages_v3.pipe | 4 ++-- .../tinybird/fixtures/analytics_events.ndjson | 1 + .../data/tinybird/pipes/filtered_sessions.pipe | 2 +- .../tinybird/pipes/filtered_sessions_v2.pipe | 2 +- .../scripts/docker-analytics-manager.js | 8 +++++--- .../tinybird/tests/api_active_visitors.yaml | 6 +++--- .../tinybird/tests/api_active_visitors_v2.yaml | 6 +++--- .../server/data/tinybird/tests/api_kpis.yaml | 10 +++++----- .../data/tinybird/tests/api_kpis_v2.yaml | 10 +++++----- .../tests/api_post_visitor_counts.yaml | 2 +- .../tests/api_post_visitor_counts_v2.yaml | 2 +- .../data/tinybird/tests/api_top_devices.yaml | 8 ++++---- .../tinybird/tests/api_top_devices_v2.yaml | 8 ++++---- .../data/tinybird/tests/api_top_locations.yaml | 6 +++++- .../tinybird/tests/api_top_locations_v2.yaml | 6 +++++- .../data/tinybird/tests/api_top_pages.yaml | 12 ++++++------ .../tinybird/tests/api_top_pages_router.yaml | 8 ++++---- .../data/tinybird/tests/api_top_pages_v2.yaml | 12 ++++++------ .../data/tinybird/tests/api_top_pages_v3.yaml | 18 ++++++++++++------ .../data/tinybird/tests/api_top_sources.yaml | 8 ++++---- .../tinybird/tests/api_top_sources_v2.yaml | 8 ++++---- 28 files changed, 104 insertions(+), 73 deletions(-) diff --git a/ghost/core/core/server/data/tinybird/ARCHITECTURE.md b/ghost/core/core/server/data/tinybird/ARCHITECTURE.md index 5a7c0f22c6b..2e515170668 100644 --- a/ghost/core/core/server/data/tinybird/ARCHITECTURE.md +++ b/ghost/core/core/server/data/tinybird/ARCHITECTURE.md @@ -71,7 +71,7 @@ Core member accounts. - `id` (string, 24 chars) - Primary key - `uuid` (string, 36 chars) - UUID for Tinybird correlation - `email` - Member email address -- `status` - 'free', 'paid', 'comped' +- `status` - 'free', 'paid', 'comped', 'gift' - `created_at` - Signup timestamp #### `newsletters` @@ -214,7 +214,7 @@ Fields with specific data in the schema: { "site_uuid": "string", "member_uuid": "string|undefined", // member.uuid in MySQL - "member_status": "free|paid|comped|undefined", // member.status in MySQL + "member_status": "free|paid|comped|gift|undefined", // member.status in MySQL. Note: when filtering Tinybird endpoints by `paid`, `comped` and `gift` are bucketed in alongside `paid`. "post_uuid": "string|undefined", // post.uuid in MySQL "post_type": "post|page|empty", //post.type in MySQL "user-agent": "string", diff --git a/ghost/core/core/server/data/tinybird/endpoints/api_kpis.pipe b/ghost/core/core/server/data/tinybird/endpoints/api_kpis.pipe index 1f57c725b5d..b195b3fa4cf 100644 --- a/ghost/core/core/server/data/tinybird/endpoints/api_kpis.pipe +++ b/ghost/core/core/server/data/tinybird/endpoints/api_kpis.pipe @@ -124,7 +124,14 @@ SQL > on fs.session_id = h.session_id where site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }} - {% if defined(member_status) %} and member_status IN {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} {% end %} + {% if defined(member_status) %} + and member_status IN ( + select arrayJoin( + {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) + ) + ) + {% end %} {% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %} {% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %} {% if defined(post_uuid) %} and post_uuid = {{String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %} diff --git a/ghost/core/core/server/data/tinybird/endpoints/api_kpis_v2.pipe b/ghost/core/core/server/data/tinybird/endpoints/api_kpis_v2.pipe index 7c8c9c21ec8..2532469a6a5 100644 --- a/ghost/core/core/server/data/tinybird/endpoints/api_kpis_v2.pipe +++ b/ghost/core/core/server/data/tinybird/endpoints/api_kpis_v2.pipe @@ -138,7 +138,14 @@ SQL > on fs.session_id = h.session_id where site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }} - {% if defined(member_status) %} and member_status IN {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} {% end %} + {% if defined(member_status) %} + and member_status IN ( + select arrayJoin( + {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) + ) + ) + {% end %} {% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %} {% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %} {% if defined(post_uuid) %} and post_uuid = {{String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %} diff --git a/ghost/core/core/server/data/tinybird/endpoints/api_top_locations.pipe b/ghost/core/core/server/data/tinybird/endpoints/api_top_locations.pipe index 9d0aa9ee9d9..a1b8f9cb173 100644 --- a/ghost/core/core/server/data/tinybird/endpoints/api_top_locations.pipe +++ b/ghost/core/core/server/data/tinybird/endpoints/api_top_locations.pipe @@ -17,7 +17,7 @@ SQL > and member_status IN ( select arrayJoin( {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} - || if('paid' IN {{ Array(member_status) }}, ['comped'], []) + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) ) ) {% end %} diff --git a/ghost/core/core/server/data/tinybird/endpoints/api_top_locations_v2.pipe b/ghost/core/core/server/data/tinybird/endpoints/api_top_locations_v2.pipe index 6d0801766da..2963b1f4a68 100644 --- a/ghost/core/core/server/data/tinybird/endpoints/api_top_locations_v2.pipe +++ b/ghost/core/core/server/data/tinybird/endpoints/api_top_locations_v2.pipe @@ -17,7 +17,7 @@ SQL > and member_status IN ( select arrayJoin( {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} - || if('paid' IN {{ Array(member_status) }}, ['comped'], []) + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) ) ) {% end %} diff --git a/ghost/core/core/server/data/tinybird/endpoints/api_top_pages.pipe b/ghost/core/core/server/data/tinybird/endpoints/api_top_pages.pipe index aafc1241770..0a168f02c39 100644 --- a/ghost/core/core/server/data/tinybird/endpoints/api_top_pages.pipe +++ b/ghost/core/core/server/data/tinybird/endpoints/api_top_pages.pipe @@ -18,7 +18,7 @@ SQL > and member_status IN ( select arrayJoin( {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} - || if('paid' IN {{ Array(member_status) }}, ['comped'], []) + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) ) ) {% end %} diff --git a/ghost/core/core/server/data/tinybird/endpoints/api_top_pages_v2.pipe b/ghost/core/core/server/data/tinybird/endpoints/api_top_pages_v2.pipe index b53cd5d2eee..567bffcd2fc 100644 --- a/ghost/core/core/server/data/tinybird/endpoints/api_top_pages_v2.pipe +++ b/ghost/core/core/server/data/tinybird/endpoints/api_top_pages_v2.pipe @@ -18,7 +18,7 @@ SQL > and member_status IN ( select arrayJoin( {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} - || if('paid' IN {{ Array(member_status) }}, ['comped'], []) + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) ) ) {% end %} diff --git a/ghost/core/core/server/data/tinybird/endpoints/api_top_pages_v3.pipe b/ghost/core/core/server/data/tinybird/endpoints/api_top_pages_v3.pipe index 6a7b087f4a7..4691057efe6 100644 --- a/ghost/core/core/server/data/tinybird/endpoints/api_top_pages_v3.pipe +++ b/ghost/core/core/server/data/tinybird/endpoints/api_top_pages_v3.pipe @@ -43,7 +43,7 @@ SQL > AND member_status IN ( SELECT arrayJoin( {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} - || if('paid' IN {{ Array(member_status) }}, ['comped'], []) + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) ) ) {% end %} @@ -80,7 +80,7 @@ SQL > AND member_status IN ( SELECT arrayJoin( {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} - || if('paid' IN {{ Array(member_status) }}, ['comped'], []) + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) ) ) {% end %} diff --git a/ghost/core/core/server/data/tinybird/fixtures/analytics_events.ndjson b/ghost/core/core/server/data/tinybird/fixtures/analytics_events.ndjson index 834d36aafc6..a8035fa6546 100644 --- a/ghost/core/core/server/data/tinybird/fixtures/analytics_events.ndjson +++ b/ghost/core/core/server/data/tinybird/fixtures/analytics_events.ndjson @@ -29,3 +29,4 @@ {"timestamp":"2100-01-06 01:28:38","session_id":"61a2896b-7cf8-4853-86a6-a0e4f87c1e21","action":"page_hit","version":"1","payload":{"site_uuid":"mock_site_uuid","member_uuid":"undefined","member_status":"undefined","post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","post_type":"post","user-agent":"Mozilla/5.0 (Windows; U; Windows NT 5.1) AppleWebKit/533.1.0 (KHTML, like Gecko) Chrome/18.0.852.0 Safari/533.1.0","device":"desktop","locale":"en-GB","location":"GB","referrer":"https://search.yahoo.com/","pathname":"/blog/hello-world/","href":"https://my-ghost-site.com/blog/hello-world/","meta":{"referrerSource":"https://search.yahoo.com/"}}} {"timestamp":"2100-01-07 01:44:10","session_id":"7f1e88e1-da8e-46df-bc69-d04fb29d603d","action":"page_hit","version":"1","payload":{"site_uuid":"mock_site_uuid","member_uuid":"undefined","member_status":"undefined","post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","post_type":"page","user-agent":"Mozilla/5.0 (Windows NT 5.0; WOW64; rv:13.9) Gecko/20100101 Firefox/13.9.7","device":"desktop","locale":"en-US","location":"US","referrer":"http://wilted-tick.com","pathname":"/about/","href":"https://my-ghost-site.com/about/?utm_source=reddit&utm_medium=social&utm_campaign=product_launch&utm_term=announcement","utm_source":"reddit","utm_medium":"social","utm_campaign":"product_launch","utm_term":"announcement","utm_content":null,"meta":{"referrerSource":"http://wilted-tick.com","received_timestamp":"2100-01-07 01:44:10.000Z"}},"inserted_at":"2100-01-07 01:44:10.250"} {"timestamp":"2100-01-07 02:23:19","session_id":"98159299-8111-4dc8-9156-bb339fe9508c","action":"page_hit","version":"1","payload":{"site_uuid":"mock_site_uuid","member_uuid":"undefined","member_status":"undefined","post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","post_type":"post","user-agent":"Mozilla/5.0 (Windows NT 5.0; WOW64; rv:13.9) Gecko/20100101 Firefox/13.9.7","device":"desktop","locale":"en-US","location":"US","referrer":"https://my-ghost-site.com","pathname":"/blog/hello-world/","href":"https://my-ghost-site.com/blog/hello-world/?utm_source=google&utm_medium=cpc&utm_campaign=holiday_promo&utm_term=black_friday&utm_content=search_ad","utm_source":"google","utm_medium":"cpc","utm_campaign":"holiday_promo","utm_term":"black_friday","utm_content":"search_ad","meta":{"referrerSource":"https://my-ghost-site.com"}}} +{"timestamp":"2100-01-07 03:15:00","session_id":"9f2c7d18-1e73-4a55-bf91-ea63dc12bb7a","action":"page_hit","version":"1","payload":{"site_uuid":"mock_site_uuid","member_uuid":"b5df84a2-1e35-4f88-9c0e-2dca7351a4e9","member_status":"gift","post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","post_type":"post","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15","device":"desktop","locale":"it-IT","location":"IT","referrer":"","pathname":"/blog/hello-world/","href":"https://my-ghost-site.com/blog/hello-world/","meta":{"referrerSource":""}}} diff --git a/ghost/core/core/server/data/tinybird/pipes/filtered_sessions.pipe b/ghost/core/core/server/data/tinybird/pipes/filtered_sessions.pipe index fc31eb7b28b..0f18d0108d5 100644 --- a/ghost/core/core/server/data/tinybird/pipes/filtered_sessions.pipe +++ b/ghost/core/core/server/data/tinybird/pipes/filtered_sessions.pipe @@ -26,7 +26,7 @@ SQL > and member_status IN ( select arrayJoin( {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} - || if('paid' IN {{ Array(member_status) }}, ['comped'], []) + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) ) ) {% end %} diff --git a/ghost/core/core/server/data/tinybird/pipes/filtered_sessions_v2.pipe b/ghost/core/core/server/data/tinybird/pipes/filtered_sessions_v2.pipe index bfb8db29fc1..6cb6fab31b9 100644 --- a/ghost/core/core/server/data/tinybird/pipes/filtered_sessions_v2.pipe +++ b/ghost/core/core/server/data/tinybird/pipes/filtered_sessions_v2.pipe @@ -26,7 +26,7 @@ SQL > and member_status IN ( select arrayJoin( {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }} - || if('paid' IN {{ Array(member_status) }}, ['comped'], []) + || if('paid' IN {{ Array(member_status) }}, ['comped', 'gift'], []) ) ) {% end %} diff --git a/ghost/core/core/server/data/tinybird/scripts/docker-analytics-manager.js b/ghost/core/core/server/data/tinybird/scripts/docker-analytics-manager.js index 86b671d0c3e..98206531334 100644 --- a/ghost/core/core/server/data/tinybird/scripts/docker-analytics-manager.js +++ b/ghost/core/core/server/data/tinybird/scripts/docker-analytics-manager.js @@ -96,9 +96,11 @@ class DockerAnalyticsManager { this.locales = ['en-US', 'en-GB', 'es-ES', 'fr-FR', 'de-DE', 'it-IT', 'pt-BR', 'ja-JP']; this.memberStatusWeights = [ - {value: 'undefined', weight: 83}, - {value: 'paid', weight: 9}, - {value: 'free', weight: 8} + {value: 'undefined', weight: 82}, + {value: 'paid', weight: 8}, + {value: 'free', weight: 8}, + {value: 'comped', weight: 1}, + {value: 'gift', weight: 1} ]; this.locationWeights = [ diff --git a/ghost/core/core/server/data/tinybird/tests/api_active_visitors.yaml b/ghost/core/core/server/data/tinybird/tests/api_active_visitors.yaml index 22547dc5c81..8f79f5d32bf 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_active_visitors.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_active_visitors.yaml @@ -3,19 +3,19 @@ description: Test active visitors with default site UUID parameters: site_uuid=mock_site_uuid expected_result: | - {"active_visitors":16} + {"active_visitors":17} - name: active_visitors_default description: Test active visitors with only required site_uuid parameter parameters: site_uuid=mock_site_uuid expected_result: | - {"active_visitors":16} + {"active_visitors":17} - name: active_visitors_with_post description: Test active visitors filtered by specific post parameters: site_uuid=mock_site_uuid&post_uuid=6b8635fb-292f-4422-9fe4-d76cfab2ba31 expected_result: | - {"active_visitors":9} + {"active_visitors":10} - name: active_visitors_custom_site description: Test active visitors with custom site UUID diff --git a/ghost/core/core/server/data/tinybird/tests/api_active_visitors_v2.yaml b/ghost/core/core/server/data/tinybird/tests/api_active_visitors_v2.yaml index 22547dc5c81..8f79f5d32bf 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_active_visitors_v2.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_active_visitors_v2.yaml @@ -3,19 +3,19 @@ description: Test active visitors with default site UUID parameters: site_uuid=mock_site_uuid expected_result: | - {"active_visitors":16} + {"active_visitors":17} - name: active_visitors_default description: Test active visitors with only required site_uuid parameter parameters: site_uuid=mock_site_uuid expected_result: | - {"active_visitors":16} + {"active_visitors":17} - name: active_visitors_with_post description: Test active visitors filtered by specific post parameters: site_uuid=mock_site_uuid&post_uuid=6b8635fb-292f-4422-9fe4-d76cfab2ba31 expected_result: | - {"active_visitors":9} + {"active_visitors":10} - name: active_visitors_custom_site description: Test active visitors with custom site UUID diff --git a/ghost/core/core/server/data/tinybird/tests/api_kpis.yaml b/ghost/core/core/server/data/tinybird/tests/api_kpis.yaml index 7bfefd0655a..5ac486445a2 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_kpis.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_kpis.yaml @@ -9,7 +9,7 @@ {"date":"2100-01-04","visits":3,"pageviews":7,"bounce_rate":0.33,"avg_session_sec":572} {"date":"2100-01-05","visits":2,"pageviews":5,"bounce_rate":0,"avg_session_sec":308} {"date":"2100-01-06","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} - {"date":"2100-01-07","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} + {"date":"2100-01-07","visits":3,"pageviews":3,"bounce_rate":1,"avg_session_sec":0} - name: Filtered by location - UK description: Filtered by location - UK @@ -60,7 +60,7 @@ {"date":"2100-01-07","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} - name: Filtered by member status - paid - description: Filtered by member status - paid + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&member_status=paid expected_result: | {"date":"2100-01-01","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} @@ -69,7 +69,7 @@ {"date":"2100-01-04","visits":1,"pageviews":1,"bounce_rate":1,"avg_session_sec":0} {"date":"2100-01-05","visits":1,"pageviews":2,"bounce_rate":0,"avg_session_sec":123} {"date":"2100-01-06","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} - {"date":"2100-01-07","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} + {"date":"2100-01-07","visits":1,"pageviews":1,"bounce_rate":1,"avg_session_sec":0} - name: Filtered by member status - undefined description: Filtered by member status - undefined @@ -92,7 +92,7 @@ {"date":"2100-01-03","visits":3,"pageviews":7,"bounce_rate":0.33,"avg_session_sec":572} {"date":"2100-01-04","visits":2,"pageviews":5,"bounce_rate":0,"avg_session_sec":308} {"date":"2100-01-05","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} - {"date":"2100-01-06","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} + {"date":"2100-01-06","visits":3,"pageviews":3,"bounce_rate":1,"avg_session_sec":0} {"date":"2100-01-07","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} - name: Single day - Date range @@ -264,4 +264,4 @@ {"date":"2100-01-04","visits":3,"pageviews":7,"bounce_rate":0.33,"avg_session_sec":572} {"date":"2100-01-05","visits":2,"pageviews":5,"bounce_rate":0,"avg_session_sec":308} {"date":"2100-01-06","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} - {"date":"2100-01-07","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} + {"date":"2100-01-07","visits":3,"pageviews":3,"bounce_rate":1,"avg_session_sec":0} diff --git a/ghost/core/core/server/data/tinybird/tests/api_kpis_v2.yaml b/ghost/core/core/server/data/tinybird/tests/api_kpis_v2.yaml index 7bfefd0655a..5ac486445a2 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_kpis_v2.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_kpis_v2.yaml @@ -9,7 +9,7 @@ {"date":"2100-01-04","visits":3,"pageviews":7,"bounce_rate":0.33,"avg_session_sec":572} {"date":"2100-01-05","visits":2,"pageviews":5,"bounce_rate":0,"avg_session_sec":308} {"date":"2100-01-06","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} - {"date":"2100-01-07","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} + {"date":"2100-01-07","visits":3,"pageviews":3,"bounce_rate":1,"avg_session_sec":0} - name: Filtered by location - UK description: Filtered by location - UK @@ -60,7 +60,7 @@ {"date":"2100-01-07","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} - name: Filtered by member status - paid - description: Filtered by member status - paid + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&member_status=paid expected_result: | {"date":"2100-01-01","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} @@ -69,7 +69,7 @@ {"date":"2100-01-04","visits":1,"pageviews":1,"bounce_rate":1,"avg_session_sec":0} {"date":"2100-01-05","visits":1,"pageviews":2,"bounce_rate":0,"avg_session_sec":123} {"date":"2100-01-06","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} - {"date":"2100-01-07","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} + {"date":"2100-01-07","visits":1,"pageviews":1,"bounce_rate":1,"avg_session_sec":0} - name: Filtered by member status - undefined description: Filtered by member status - undefined @@ -92,7 +92,7 @@ {"date":"2100-01-03","visits":3,"pageviews":7,"bounce_rate":0.33,"avg_session_sec":572} {"date":"2100-01-04","visits":2,"pageviews":5,"bounce_rate":0,"avg_session_sec":308} {"date":"2100-01-05","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} - {"date":"2100-01-06","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} + {"date":"2100-01-06","visits":3,"pageviews":3,"bounce_rate":1,"avg_session_sec":0} {"date":"2100-01-07","visits":0,"pageviews":0,"bounce_rate":0,"avg_session_sec":0} - name: Single day - Date range @@ -264,4 +264,4 @@ {"date":"2100-01-04","visits":3,"pageviews":7,"bounce_rate":0.33,"avg_session_sec":572} {"date":"2100-01-05","visits":2,"pageviews":5,"bounce_rate":0,"avg_session_sec":308} {"date":"2100-01-06","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} - {"date":"2100-01-07","visits":2,"pageviews":2,"bounce_rate":1,"avg_session_sec":0} + {"date":"2100-01-07","visits":3,"pageviews":3,"bounce_rate":1,"avg_session_sec":0} diff --git a/ghost/core/core/server/data/tinybird/tests/api_post_visitor_counts.yaml b/ghost/core/core/server/data/tinybird/tests/api_post_visitor_counts.yaml index 9f2f0c0f78e..46c1dfaaef6 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_post_visitor_counts.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_post_visitor_counts.yaml @@ -9,7 +9,7 @@ description: Test visitor counts for multiple post UUIDs parameters: site_uuid=mock_site_uuid&post_uuids=06b1b0c9-fb53-4a15-a060-3db3fde7b1fc,6b8635fb-292f-4422-9fe4-d76cfab2ba31,06b1b0c9-fb53-4a15-a060-3db3fde7b1dd expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","visits":8} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","visits":1} diff --git a/ghost/core/core/server/data/tinybird/tests/api_post_visitor_counts_v2.yaml b/ghost/core/core/server/data/tinybird/tests/api_post_visitor_counts_v2.yaml index 9f2f0c0f78e..46c1dfaaef6 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_post_visitor_counts_v2.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_post_visitor_counts_v2.yaml @@ -9,7 +9,7 @@ description: Test visitor counts for multiple post UUIDs parameters: site_uuid=mock_site_uuid&post_uuids=06b1b0c9-fb53-4a15-a060-3db3fde7b1fc,6b8635fb-292f-4422-9fe4-d76cfab2ba31,06b1b0c9-fb53-4a15-a060-3db3fde7b1dd expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","visits":8} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","visits":1} diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_devices.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_devices.yaml index 8959cbed00e..9519549ba20 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_devices.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_devices.yaml @@ -3,14 +3,14 @@ description: All fixture data parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC expected_result: | - {"device":"desktop","visits":15} + {"device":"desktop","visits":16} {"device":"bot","visits":1} - name: Filtered by device - desktop description: Filtered by device - desktop parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&device=desktop expected_result: | - {"device":"desktop","visits":15} + {"device":"desktop","visits":16} - name: Filtered by device - bot description: Filtered by device - bot @@ -32,10 +32,10 @@ {"device":"desktop","visits":2} - name: Filtered by member status - paid - description: Filtered by member status - paid (includes comped) + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=paid expected_result: | - {"device":"desktop","visits":5} + {"device":"desktop","visits":6} - name: Filtered by member status - free description: Filtered by member status - free diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_devices_v2.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_devices_v2.yaml index 8959cbed00e..9519549ba20 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_devices_v2.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_devices_v2.yaml @@ -3,14 +3,14 @@ description: All fixture data parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC expected_result: | - {"device":"desktop","visits":15} + {"device":"desktop","visits":16} {"device":"bot","visits":1} - name: Filtered by device - desktop description: Filtered by device - desktop parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&device=desktop expected_result: | - {"device":"desktop","visits":15} + {"device":"desktop","visits":16} - name: Filtered by device - bot description: Filtered by device - bot @@ -32,10 +32,10 @@ {"device":"desktop","visits":2} - name: Filtered by member status - paid - description: Filtered by member status - paid (includes comped) + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=paid expected_result: | - {"device":"desktop","visits":5} + {"device":"desktop","visits":6} - name: Filtered by member status - free description: Filtered by member status - free diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_locations.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_locations.yaml index f28714afc6e..0935452092d 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_locations.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_locations.yaml @@ -8,6 +8,7 @@ {"location":"FR","visits":2} {"location":"ES","visits":2} {"location":"DE","visits":1} + {"location":"IT","visits":1} - name: Filtered by location - UK description: Filtered by location - UK @@ -43,13 +44,14 @@ {"location":"GB","visits":1} - name: Filtered by member status - paid - description: Filtered by member status - paid + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=paid expected_result: | {"location":"GB","visits":2} {"location":"FR","visits":1} {"location":"DE","visits":1} {"location":"ES","visits":1} + {"location":"IT","visits":1} - name: Filtered by member status - undefined description: Filtered by member status - undefined @@ -67,6 +69,7 @@ {"location":"FR","visits":2} {"location":"DE","visits":1} {"location":"ES","visits":1} + {"location":"IT","visits":1} - name: Test with multiple filters combined description: Test with multiple filters combined @@ -84,3 +87,4 @@ {"location":"FR","visits":2} {"location":"ES","visits":2} {"location":"DE","visits":1} + {"location":"IT","visits":1} diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_locations_v2.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_locations_v2.yaml index f28714afc6e..0935452092d 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_locations_v2.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_locations_v2.yaml @@ -8,6 +8,7 @@ {"location":"FR","visits":2} {"location":"ES","visits":2} {"location":"DE","visits":1} + {"location":"IT","visits":1} - name: Filtered by location - UK description: Filtered by location - UK @@ -43,13 +44,14 @@ {"location":"GB","visits":1} - name: Filtered by member status - paid - description: Filtered by member status - paid + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=paid expected_result: | {"location":"GB","visits":2} {"location":"FR","visits":1} {"location":"DE","visits":1} {"location":"ES","visits":1} + {"location":"IT","visits":1} - name: Filtered by member status - undefined description: Filtered by member status - undefined @@ -67,6 +69,7 @@ {"location":"FR","visits":2} {"location":"DE","visits":1} {"location":"ES","visits":1} + {"location":"IT","visits":1} - name: Test with multiple filters combined description: Test with multiple filters combined @@ -84,3 +87,4 @@ {"location":"FR","visits":2} {"location":"ES","visits":2} {"location":"DE","visits":1} + {"location":"IT","visits":1} diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_pages.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_pages.yaml index 2d0d80a6970..89ff7b85d86 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_pages.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_pages.yaml @@ -3,7 +3,7 @@ description: All fixture data parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} {"post_uuid":"","pathname":"\/","visits":7} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} @@ -37,11 +37,11 @@ {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":1} - name: Filtered by member status - paid - description: Filtered by member status - paid + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=paid expected_result: | + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":4} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":3} - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":3} {"post_uuid":"","pathname":"\/","visits":2} - name: Filtered by member status - undefined @@ -57,7 +57,7 @@ description: Filtered by timezone - America/Los_Angeles parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=America/Los_Angeles expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":8} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":7} {"post_uuid":"","pathname":"\/","visits":5} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} @@ -72,7 +72,7 @@ description: Test with post_type - post parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&post_type=post expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} - name: Test with post_type - page @@ -129,7 +129,7 @@ description: Filtered by device - desktop (excludes bot session) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&device=desktop expected_result: | + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":8} {"post_uuid":"","pathname":"\/","visits":7} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_pages_router.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_pages_router.yaml index f28f7cc7a91..f685fcb6c1e 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_pages_router.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_pages_router.yaml @@ -3,7 +3,7 @@ description: Query without session-level filters (should route to optimized pipe) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} {"post_uuid":"","pathname":"\/","visits":7} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} @@ -18,14 +18,14 @@ description: Query with post_uuid filter (should route to optimized pipe) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&post_uuid=6b8635fb-292f-4422-9fe4-d76cfab2ba31 expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} - name: device_filter description: Query with device filter routes to api_top_pages parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&device=desktop expected_result: | + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":8} {"post_uuid":"","pathname":"\/","visits":7} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} @@ -62,7 +62,7 @@ data up to date_to parameters: site_uuid=mock_site_uuid&date_to=2100-01-07&timezone=Etc/UTC expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} {"post_uuid":"","pathname":"\/","visits":7} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_pages_v2.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_pages_v2.yaml index 2d0d80a6970..89ff7b85d86 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_pages_v2.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_pages_v2.yaml @@ -3,7 +3,7 @@ description: All fixture data parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} {"post_uuid":"","pathname":"\/","visits":7} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} @@ -37,11 +37,11 @@ {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":1} - name: Filtered by member status - paid - description: Filtered by member status - paid + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=paid expected_result: | + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":4} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":3} - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":3} {"post_uuid":"","pathname":"\/","visits":2} - name: Filtered by member status - undefined @@ -57,7 +57,7 @@ description: Filtered by timezone - America/Los_Angeles parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=America/Los_Angeles expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":8} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":7} {"post_uuid":"","pathname":"\/","visits":5} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} @@ -72,7 +72,7 @@ description: Test with post_type - post parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&post_type=post expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} - name: Test with post_type - page @@ -129,7 +129,7 @@ description: Filtered by device - desktop (excludes bot session) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&device=desktop expected_result: | + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":8} {"post_uuid":"","pathname":"\/","visits":7} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_pages_v3.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_pages_v3.yaml index fe20a455905..23d340e7cd7 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_pages_v3.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_pages_v3.yaml @@ -3,7 +3,7 @@ description: All fixture data - uses pre-aggregated daily MV parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} {"post_uuid":"","pathname":"\/","visits":7} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} @@ -21,11 +21,11 @@ {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} - name: Filtered by member status - paid - description: Filtered by member status - paid (includes comped) + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=paid expected_result: | + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":4} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":3} - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":3} {"post_uuid":"","pathname":"\/","visits":2} - name: Filtered by member status - free @@ -45,11 +45,17 @@ {"post_uuid":"","pathname":"\/","visits":1} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} +- name: Filtered by member status - gift + description: Filtered explicitly by gift status (returns only gift sessions; gift is also included implicitly when filtering by paid) + parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=gift + expected_result: | + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":1} + - name: Filtered by timezone - America/Los_Angeles description: Filtered by timezone - America/Los_Angeles (8 hours behind UTC) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=America/Los_Angeles expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} {"post_uuid":"","pathname":"\/","visits":7} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} @@ -58,7 +64,7 @@ description: Test with post_type - post parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&post_type=post expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1dd","pathname":"\/blog\/hello-world\/","visits":1} - name: Test with post_type - page @@ -92,7 +98,7 @@ description: Test pagination with limit parameter parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&limit=2 expected_result: | - {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":9} + {"post_uuid":"6b8635fb-292f-4422-9fe4-d76cfab2ba31","pathname":"\/blog\/hello-world\/","visits":10} {"post_uuid":"06b1b0c9-fb53-4a15-a060-3db3fde7b1fc","pathname":"\/about\/","visits":8} - name: Single day query diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_sources.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_sources.yaml index 7e6c8914b96..70c26012819 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_sources.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_sources.yaml @@ -3,7 +3,7 @@ description: All fixture data parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC expected_result: | - {"source":"","visits":6} + {"source":"","visits":7} {"source":"bing.com","visits":2} {"source":"search.yahoo.com","visits":2} {"source":"google.com","visits":1} @@ -49,10 +49,10 @@ {"source":"bing.com","visits":2} - name: Filtered by member status - paid - description: Filtered by member status - paid + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=paid expected_result: | - {"source":"","visits":2} + {"source":"","visits":3} {"source":"bing.com","visits":1} {"source":"google.com","visits":1} {"source":"search.yahoo.com","visits":1} @@ -72,7 +72,7 @@ description: Filtered by timezone - America/Los_Angeles parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=America/Los_Angeles expected_result: | - {"source":"","visits":5} + {"source":"","visits":6} {"source":"search.yahoo.com","visits":2} {"source":"bing.com","visits":1} {"source":"google.com","visits":1} diff --git a/ghost/core/core/server/data/tinybird/tests/api_top_sources_v2.yaml b/ghost/core/core/server/data/tinybird/tests/api_top_sources_v2.yaml index 7e6c8914b96..70c26012819 100644 --- a/ghost/core/core/server/data/tinybird/tests/api_top_sources_v2.yaml +++ b/ghost/core/core/server/data/tinybird/tests/api_top_sources_v2.yaml @@ -3,7 +3,7 @@ description: All fixture data parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC expected_result: | - {"source":"","visits":6} + {"source":"","visits":7} {"source":"bing.com","visits":2} {"source":"search.yahoo.com","visits":2} {"source":"google.com","visits":1} @@ -49,10 +49,10 @@ {"source":"bing.com","visits":2} - name: Filtered by member status - paid - description: Filtered by member status - paid + description: Filtered by member status - paid (includes comped and gift) parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=Etc/UTC&member_status=paid expected_result: | - {"source":"","visits":2} + {"source":"","visits":3} {"source":"bing.com","visits":1} {"source":"google.com","visits":1} {"source":"search.yahoo.com","visits":1} @@ -72,7 +72,7 @@ description: Filtered by timezone - America/Los_Angeles parameters: site_uuid=mock_site_uuid&date_from=2100-01-01&date_to=2100-01-07&timezone=America/Los_Angeles expected_result: | - {"source":"","visits":5} + {"source":"","visits":6} {"source":"search.yahoo.com","visits":2} {"source":"bing.com","visits":1} {"source":"google.com","visits":1} From caca37de53ab7d2f4a214ceb970086fe93828206 Mon Sep 17 00:00:00 2001 From: Chris Raible Date: Mon, 4 May 2026 14:37:15 -0700 Subject: [PATCH 4/4] Added secret scanning pre-commit hook (#27609) ref https://linear.app/tryghost/issue/NY-1247/add-a-git-pre-commit-hook-to-check-for-secrets-in-staged - Added secret lint dependency & configuration - Added pre-commit hook that scans staged changes for secrets, blocking commits that fail the check --------- Co-authored-by: Steve Larson <9larsons@gmail.com> --- .github/hooks/pre-commit.bash | 66 +++++- .secretlintrc.json | 38 ++++ package.json | 3 + pnpm-lock.yaml | 403 ++++++++++++++++++++++++++++++++++ 4 files changed, 504 insertions(+), 6 deletions(-) create mode 100644 .secretlintrc.json diff --git a/.github/hooks/pre-commit.bash b/.github/hooks/pre-commit.bash index 48a30898b53..79d21fc09ad 100755 --- a/.github/hooks/pre-commit.bash +++ b/.github/hooks/pre-commit.bash @@ -3,6 +3,11 @@ [ -n "$CI" ] && exit 0 +green='\033[0;32m' +no_color='\033[0m' +grey='\033[0;90m' +red='\033[0;31m' + pnpm lint-staged --relative lintStatus=$? @@ -11,13 +16,62 @@ if [ $lintStatus -ne 0 ]; then exit 1 fi -green='\033[0;32m' -no_color='\033[0m' -grey='\033[0;90m' -red='\033[0;31m' +## +## 1) Scan staged text files for secrets +## + +scan_staged_secrets() { + local file + local files_scanned=0 + local scan_status=0 + local tmpfile + + if ! pnpm exec secretlint --version >/dev/null 2>&1; then + echo -e "${red}secretlint is not available. Run pnpm install from the repository root.${no_color}" + return 1 + fi + + if ! tmpfile=$(mktemp); then + echo -e "${red}Could not create temp file for secret scanning${no_color}" + return 1 + fi + + echo -e "Scanning staged files for secrets ${grey}(pre-commit hook)${no_color} " + + while IFS= read -r -d '' file; do + if ! git show ":$file" > "$tmpfile"; then + scan_status=1 + continue + fi + + if LC_ALL=C grep -Iq . "$tmpfile"; then + files_scanned=$((files_scanned + 1)) + + if ! pnpm exec secretlint --format=compact --stdinFileName="$file" < "$tmpfile"; then + scan_status=1 + fi + fi + done < <(git diff --cached --name-only --diff-filter=ACMR -z) + + if [ $files_scanned -eq 0 ]; then + echo "No staged text files to scan, continuing..." + fi + + rm -f "$tmpfile" + + return $scan_status +} + +scan_staged_secrets +secretScanStatus=$? + +if [ $secretScanStatus -ne 0 ]; then + echo -e "${red}❌ Secret scanning failed${no_color}" + exit 1 +fi ## -## 1) Check and remove submodules before committing +## 2) Check and remove submodules before committing ## ROOT_DIR=$(git rev-parse --show-cdup) @@ -47,7 +101,7 @@ else fi ## -## 2) Suggest shipping a new version of @tryghost/activitypub when changes are detected +## 3) Suggest shipping a new version of @tryghost/activitypub when changes are detected ## The intent is to ship smaller changes more frequently to production ## diff --git a/.secretlintrc.json b/.secretlintrc.json new file mode 100644 index 00000000000..fcef33c49ab --- /dev/null +++ b/.secretlintrc.json @@ -0,0 +1,38 @@ +{ + "rules": [ + { + "id": "@secretlint/secretlint-rule-preset-recommend" + }, + { + "id": "@secretlint/secretlint-rule-pattern", + "options": { + "patterns": [ + { + "name": "Tinybird token", + "patterns": [ + "/\\b(?p\\.eyJ[A-Za-z0-9_-]{15,}\\.[A-Za-z0-9_-]{20,})\\b/" + ] + }, + { + "name": "credential in URL query string", + "patterns": [ + "/[?&](?:token|api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|secret|password)=(?(?!p\\.)[^&\\s\"'<>]{16,})/i" + ] + }, + { + "name": "credential assignment", + "patterns": [ + "/(?:^|[\\s{\"',])\\b(?:api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|secret[_-]?key|private[_-]?key|password)\\b\\s*[:=]\\s*[\"']?(?[A-Za-z0-9_./+=:-]{20,})[\"']?/i" + ] + }, + { + "name": "authorization header", + "patterns": [ + "/\\bAuthorization\\s*:\\s*(?:Bearer|Basic)\\s+(?[A-Za-z0-9_./+=:-]{20,})/i" + ] + } + ] + } + } + ] +} diff --git a/package.json b/package.json index c4c1bbece94..e12c34348ac 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,8 @@ "devDependencies": { "@actions/core": "3.0.0", "@playwright/test": "1.59.1", + "@secretlint/secretlint-rule-pattern": "12.3.1", + "@secretlint/secretlint-rule-preset-recommend": "12.3.1", "chalk": "4.1.2", "chokidar": "3.6.0", "eslint": "catalog:", @@ -130,6 +132,7 @@ "lint-staged": "16.4.0", "nx": "22.0.4", "rimraf": "6.1.3", + "secretlint": "12.3.1", "semver": "7.7.4", "typescript": "5.9.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22d52b920ca..368af77280b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,12 @@ importers: '@playwright/test': specifier: 1.59.1 version: 1.59.1 + '@secretlint/secretlint-rule-pattern': + specifier: 12.3.1 + version: 12.3.1 + '@secretlint/secretlint-rule-preset-recommend': + specifier: 12.3.1 + version: 12.3.1 chalk: specifier: 4.1.2 version: 4.1.2 @@ -104,6 +110,9 @@ importers: rimraf: specifier: 6.1.3 version: 6.1.3 + secretlint: + specifier: 12.3.1 + version: 12.3.1 semver: specifier: 7.7.4 version: 7.7.4 @@ -2916,6 +2925,12 @@ packages: resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} engines: {node: '>=18.0.0'} + '@azu/format-text@1.0.2': + resolution: {integrity: sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==} + + '@azu/style-format@1.0.1': + resolution: {integrity: sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -6554,6 +6569,52 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@secretlint/config-creator@12.3.1': + resolution: {integrity: sha512-CCRvPfrQLt2fPg3eWTIDGXNcVFQd6ZnvQCZ5lzclV9OF7iRqXQ4l5lfGO8NS68tIZx7YvBKhcO8/eVxdqm89HA==} + engines: {node: '>=22.0.0'} + + '@secretlint/config-loader@12.3.1': + resolution: {integrity: sha512-PNrxz8tnAU/y5PmfOtKfVb+zEA3I+1iZqP1f9fXvIBtauBKs0h0Y+Cmvj0gG1a34kxaD1aQvFh8qHEhRV05gWg==} + engines: {node: '>=22.0.0'} + + '@secretlint/core@12.3.1': + resolution: {integrity: sha512-ulcfARo1TANr8tWzDO/5cFxSNEEfRzgW6YPHYUijgpH3iYfwtUhWEU/r/BiFGl2PNaGzzVE1N9A6nZ74xbYvUQ==} + engines: {node: '>=22.0.0'} + + '@secretlint/formatter@12.3.1': + resolution: {integrity: sha512-SXpTiRzuuFNbHa59zk0eUxFOB/LYxnuHSfqq7zU9lIt0z5rox6NrnN9WWkoQai2V9s7n3VqUVUpZqnhxQ2Jzpw==} + engines: {node: '>=22.0.0'} + + '@secretlint/node@12.3.1': + resolution: {integrity: sha512-1T08nqwWIJqSRrfkebk4Op5MwYgNnB6gwjv9v+X+V+HEIeG1GB/EgH8CJa8jK4uYdhUuaKyXpu36FIbjNa1wqA==} + engines: {node: '>=22.0.0'} + + '@secretlint/profiler@12.3.1': + resolution: {integrity: sha512-lztyqJPTfkY0Ze9P7vNs3zm7p2Wq1+4ilFXVrxin0sDyFVXpkt+0+vsKsmdx9yBHabxrLDZgxa7fIsfV721cLw==} + + '@secretlint/resolver@12.3.1': + resolution: {integrity: sha512-/QwcX5azKRdz9mBIbTBUsqp+cmWQZYGNdOHLbsMOBTLXa7KoEBffhmeaMSc0kNSrdgbgfu/7j+qeeaF4QwJf3A==} + + '@secretlint/secretlint-rule-pattern@12.3.1': + resolution: {integrity: sha512-s7YqD/gzdv9XY1kBaH3kbYIvAvV3Ygl50/sh56P3rVUPcaKAhBk5xlxEskHIdUOZsSJJpBfiafTF6nCLzN6dsw==} + engines: {node: '>=22.0.0'} + + '@secretlint/secretlint-rule-preset-recommend@12.3.1': + resolution: {integrity: sha512-w9x9rIP1+qhV0k9k15aSIBo+6NrM4npAYIoNs7UmKIxuQSA6b91rhWpdMxgCv2/EQtym+gpuyN7rTSvG5wlYlw==} + engines: {node: '>=22.0.0'} + + '@secretlint/source-creator@12.3.1': + resolution: {integrity: sha512-RCkmyKdoe6VFWMzzVm5a9W+a+ptJSusVX+YOrcNy/heklMIWLg0bL+HYFcyYCm8rU2dRq2HuSYTOamDjNs0LZg==} + engines: {node: '>=22.0.0'} + + '@secretlint/tester@12.3.1': + resolution: {integrity: sha512-DFnJ8srTTsyyti543T5SEvqmZp/c/1JfFMYUbAsFyzLuDgDkvMUEE6m6FQrYl0p/DEIL3k30LgnskmmDxO9VtQ==} + engines: {node: '>=22.0.0'} + + '@secretlint/types@12.3.1': + resolution: {integrity: sha512-Qv3fKvPkzUJpS9Ps6m2EPjC0RdxS2ZZrRfZAhIdl2u0zSjgf+Z0+AaCngmHRR+3Vtbw6s2FrCf4T6mLirm8Hgg==} + engines: {node: '>=22.0.0'} + '@selderee/plugin-htmlparser2@0.6.0': resolution: {integrity: sha512-J3jpy002TyBjd4N/p6s+s90eX42H2eRhK3SbsZuvTDv977/E8p2U3zikdiehyJja66do7FlxLomZLPlvl2/xaA==} @@ -8297,6 +8358,24 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' + '@textlint/ast-node-types@15.6.0': + resolution: {integrity: sha512-CxZHFbYAU7J0A4izz31wV2ZZfySR6aVj2OSR6/3tppZm7VV6hM7nA7sutsLwIiBL/v4lsB1RM79l4Dc/VrH4qw==} + + '@textlint/linter-formatter@15.6.0': + resolution: {integrity: sha512-IwHRhjwxs0a5t1eNAoKAdV224CDca38LyopPofXpwO/d0J75wBvzf/cBHXNl4TMsLKhYGtR83UprcLEKj/gZsA==} + + '@textlint/module-interop@15.6.0': + resolution: {integrity: sha512-MHY6pJx9i5kOlrvUSK51887tYZjHAV2qnr6unBm7LtBLGDFo93utdYqHyWep8r9QLsilQdeijWtufJI46z4v4w==} + + '@textlint/regexp-string-matcher@2.0.2': + resolution: {integrity: sha512-OXLD9XRxMhd3S0LWuPHpiARQOI7z9tCOs0FsynccW2lmyZzHHFJ9/eR6kuK9xF459Qf+740qI5h+/0cx+NljzA==} + + '@textlint/resolver@15.6.0': + resolution: {integrity: sha512-T1l2Gd3455pwtm0cTewhX/LLy3bL9z6/Fu/am+jj+jjGfXVoknYkjfkZEKrjHlA7xzay0EfUKnu//teYemLeZw==} + + '@textlint/types@15.6.0': + resolution: {integrity: sha512-CvgYb1PiqF4BGyoZebGWzAJCZ4ChJAZ9gtWjpQIMKE4Xe2KlSwDA8m8MsiZIV321f5Ibx38BMjC1Z/2ZYP2GQg==} + '@tinybirdco/charts@0.2.4': resolution: {integrity: sha512-0N8fYPY8uxO5KcBkW5Dgmapj4AqcWgyCmWnZBYsugXnnbx4/yg//f3/9oMWnM3RZ6ltt9moltYIzF/95NO2zNg==} peerDependencies: @@ -10440,6 +10519,10 @@ packages: resolution: {integrity: sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==} engines: {node: '>=0.8'} + binaryextensions@6.11.0: + resolution: {integrity: sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==} + engines: {node: '>=4'} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -10501,6 +10584,9 @@ packages: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + boundary@2.0.0: + resolution: {integrity: sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==} + bower-config@1.4.3: resolution: {integrity: sha512-MVyyUk3d1S7d2cl6YISViwJBc2VXCkxF5AUFykvN0PQj5FsUiMNSgAYTso18oRFfyZ6XEtjrgg9MAaufHbOwNw==} engines: {node: '>=0.8.0'} @@ -12705,6 +12791,10 @@ packages: resolution: {integrity: sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==} engines: {node: '>=0.8'} + editions@6.22.0: + resolution: {integrity: sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==} + engines: {ecmascript: '>= es5', node: '>=4'} + editorconfig@1.0.7: resolution: {integrity: sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==} engines: {node: '>=14'} @@ -14566,6 +14656,10 @@ packages: resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + globby@16.2.0: + resolution: {integrity: sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==} + engines: {node: '>=20'} + globjoin@0.1.4: resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} @@ -14660,6 +14754,10 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-flag@5.0.1: + resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==} + engines: {node: '>=12'} + has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -14775,6 +14873,10 @@ packages: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} + hosted-git-info@9.0.2: + resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} + engines: {node: ^20.17.0 || >=22.9.0} + hpagent@1.2.0: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} @@ -15062,6 +15164,10 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + indexes-of@1.0.1: resolution: {integrity: sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==} @@ -15412,6 +15518,10 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-path-inside@4.0.0: + resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} + engines: {node: '>=12'} + is-plain-obj@1.1.0: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} @@ -15653,6 +15763,10 @@ packages: resolution: {integrity: sha512-kT1g2zxZ5Tdabtpp9VSdOzW9lb6LXImyWbzbQeTxoRtHhurC9Ej9Wckngr2+uepPL09ky/mJHmN9jeJPML5t6A==} engines: {node: '>=0.12'} + istextorbinary@9.5.0: + resolution: {integrity: sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==} + engines: {node: '>=4'} + iterator.prototype@1.1.5: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} @@ -16627,6 +16741,9 @@ packages: lodash.some@4.6.0: resolution: {integrity: sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==} + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash.template@4.5.0: resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} deprecated: This package is deprecated. Use https://socket.dev/npm/package/eta instead. @@ -16643,6 +16760,9 @@ packages: lodash.uniqby@4.5.0: resolution: {integrity: sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==} + lodash.uniqwith@4.5.0: + resolution: {integrity: sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==} + lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} @@ -17558,6 +17678,10 @@ packages: resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} engines: {node: '>=10'} + normalize-package-data@8.0.0: + resolution: {integrity: sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==} + engines: {node: ^20.17.0 || >=22.9.0} + normalize-path@2.1.1: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} engines: {node: '>=0.10.0'} @@ -17984,6 +18108,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse-ms@1.0.1: resolution: {integrity: sha512-LpH1Cf5EYuVjkBvCDBYvkUPh+iv2bk3FHflxHkpCYT0/FZ1d3N3uJaLiHr4yGuMcFUhv6eAivitTvWZI4B/chg==} engines: {node: '>=0.10.0'} @@ -18266,6 +18394,9 @@ packages: engines: {node: '>=18'} hasBin: true + pluralize@2.0.0: + resolution: {integrity: sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -19239,6 +19370,9 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} + rc-config-loader@4.1.4: + resolution: {integrity: sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -19405,6 +19539,10 @@ packages: resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} engines: {node: '>=12'} + read-pkg@10.1.0: + resolution: {integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==} + engines: {node: '>=20'} + read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} engines: {node: '>=4'} @@ -19940,6 +20078,11 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} + secretlint@12.3.1: + resolution: {integrity: sha512-wv8TKCjU5hbBxo5jKEX8wIE78VAoL0Ux7pu18+TxtbICMZ2OCbu6EmO3OJLbUbyfUXSPVryNLNmGVgvwY6Z0xw==} + engines: {node: '>=22.0.0'} + hasBin: true + section-tests@1.3.1: resolution: {integrity: sha512-cZFz5XcvYzdHUCOwwrrSFPA9KZdukGENO7ukOgxpcScZaM8zRMIyR6LRySdgoX0nD7NH+XY85F1pQ7n89CZliw==} hasBin: true @@ -20598,6 +20741,9 @@ packages: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} engines: {node: '>=10'} + structured-source@4.0.0: + resolution: {integrity: sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==} + style-loader@2.0.0: resolution: {integrity: sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==} engines: {node: '>= 10.13.0'} @@ -20662,6 +20808,10 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -20682,6 +20832,10 @@ packages: resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} engines: {node: '>=14.18'} + supports-hyperlinks@4.4.0: + resolution: {integrity: sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==} + engines: {node: '>=20'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -20817,6 +20971,10 @@ packages: resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} engines: {node: '>=6.0.0'} + terminal-link@5.0.0: + resolution: {integrity: sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA==} + engines: {node: '>=20'} + terser-webpack-plugin@1.4.6: resolution: {integrity: sha512-2lBVf/VMVIddjSn3GqbT90GvIJ/eYXJkt8cTzU7NbjKqK8fwv18Ftr4PlbF46b/e88743iZFL5Dtr/rC4hjIeA==} engines: {node: '>= 6.9.0'} @@ -20877,6 +21035,10 @@ packages: resolution: {integrity: sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==} engines: {node: '>=0.8'} + textextensions@6.11.0: + resolution: {integrity: sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==} + engines: {node: '>=4'} + thenby@1.3.4: resolution: {integrity: sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==} @@ -21391,6 +21553,10 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unicorn-magic@0.4.0: + resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} + engines: {node: '>=20'} + unidecode@0.1.8: resolution: {integrity: sha512-SdoZNxCWpN2tXTCrGkPF/0rL2HEq+i2gwRG1ReBvx8/0yTzC3enHfugOf8A9JBShVwwrRIkLX0YcDUGbzjbVCA==} engines: {node: '>= 0.4.12'} @@ -21663,6 +21829,10 @@ packages: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} + version-range@4.15.0: + resolution: {integrity: sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==} + engines: {node: '>=4'} + vfile-location@2.0.6: resolution: {integrity: sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==} @@ -23072,6 +23242,12 @@ snapshots: '@aws/lambda-invoke-store@0.2.4': {} + '@azu/format-text@1.0.2': {} + + '@azu/style-format@1.0.1': + dependencies: + '@azu/format-text': 1.0.2 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -27154,6 +27330,90 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} + '@secretlint/config-creator@12.3.1': + dependencies: + '@secretlint/types': 12.3.1 + + '@secretlint/config-loader@12.3.1': + dependencies: + '@secretlint/profiler': 12.3.1 + '@secretlint/resolver': 12.3.1 + '@secretlint/types': 12.3.1 + ajv: 8.18.0 + debug: 4.4.3(supports-color@5.5.0) + rc-config-loader: 4.1.4 + transitivePeerDependencies: + - supports-color + + '@secretlint/core@12.3.1': + dependencies: + '@secretlint/profiler': 12.3.1 + '@secretlint/types': 12.3.1 + debug: 4.4.3(supports-color@5.5.0) + structured-source: 4.0.0 + transitivePeerDependencies: + - supports-color + + '@secretlint/formatter@12.3.1': + dependencies: + '@secretlint/resolver': 12.3.1 + '@secretlint/types': 12.3.1 + '@textlint/linter-formatter': 15.6.0 + '@textlint/module-interop': 15.6.0 + '@textlint/types': 15.6.0 + chalk: 5.6.2 + debug: 4.4.3(supports-color@5.5.0) + pluralize: 8.0.0 + strip-ansi: 7.2.0 + table: 6.9.0 + terminal-link: 5.0.0 + transitivePeerDependencies: + - supports-color + + '@secretlint/node@12.3.1': + dependencies: + '@secretlint/config-loader': 12.3.1 + '@secretlint/core': 12.3.1 + '@secretlint/formatter': 12.3.1 + '@secretlint/profiler': 12.3.1 + '@secretlint/source-creator': 12.3.1 + '@secretlint/types': 12.3.1 + debug: 4.4.3(supports-color@5.5.0) + p-map: 7.0.4 + transitivePeerDependencies: + - supports-color + + '@secretlint/profiler@12.3.1': {} + + '@secretlint/resolver@12.3.1': {} + + '@secretlint/secretlint-rule-pattern@12.3.1': + dependencies: + '@secretlint/tester': 12.3.1 + '@secretlint/types': 12.3.1 + '@textlint/regexp-string-matcher': 2.0.2 + micromatch: 4.0.8 + transitivePeerDependencies: + - supports-color + + '@secretlint/secretlint-rule-preset-recommend@12.3.1': {} + + '@secretlint/source-creator@12.3.1': + dependencies: + '@secretlint/types': 12.3.1 + istextorbinary: 9.5.0 + + '@secretlint/tester@12.3.1': + dependencies: + '@secretlint/config-loader': 12.3.1 + '@secretlint/core': 12.3.1 + '@secretlint/source-creator': 12.3.1 + '@secretlint/types': 12.3.1 + transitivePeerDependencies: + - supports-color + + '@secretlint/types@12.3.1': {} + '@selderee/plugin-htmlparser2@0.6.0': dependencies: domhandler: 4.3.1 @@ -29301,6 +29561,42 @@ snapshots: dependencies: '@testing-library/dom': 10.4.0 + '@textlint/ast-node-types@15.6.0': {} + + '@textlint/linter-formatter@15.6.0': + dependencies: + '@azu/format-text': 1.0.2 + '@azu/style-format': 1.0.1 + '@textlint/module-interop': 15.6.0 + '@textlint/resolver': 15.6.0 + '@textlint/types': 15.6.0 + chalk: 4.1.2 + debug: 4.4.3(supports-color@5.5.0) + js-yaml: 4.1.1 + lodash: 4.18.1 + pluralize: 2.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + table: 6.9.0 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + '@textlint/module-interop@15.6.0': {} + + '@textlint/regexp-string-matcher@2.0.2': + dependencies: + escape-string-regexp: 4.0.0 + lodash.sortby: 4.7.0 + lodash.uniq: 4.5.0 + lodash.uniqwith: 4.5.0 + + '@textlint/resolver@15.6.0': {} + + '@textlint/types@15.6.0': + dependencies: + '@textlint/ast-node-types': 15.6.0 + '@tinybirdco/charts@0.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: echarts: 5.6.0 @@ -32562,6 +32858,10 @@ snapshots: binaryextensions@2.3.0: {} + binaryextensions@6.11.0: + dependencies: + editions: 6.22.0 + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -32670,6 +32970,8 @@ snapshots: boolean@3.2.0: {} + boundary@2.0.0: {} + bower-config@1.4.3: dependencies: graceful-fs: 4.2.11 @@ -35190,6 +35492,10 @@ snapshots: editions@1.3.4: {} + editions@6.22.0: + dependencies: + version-range: 4.15.0 + editorconfig@1.0.7: dependencies: '@one-ini/wasm': 0.1.1 @@ -38597,6 +38903,15 @@ snapshots: merge2: 1.4.1 slash: 4.0.0 + globby@16.2.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + fast-glob: 3.3.3 + ignore: 7.0.5 + is-path-inside: 4.0.0 + slash: 5.1.0 + unicorn-magic: 0.4.0 + globjoin@0.1.4: {} globrex@0.1.2: {} @@ -38725,6 +39040,8 @@ snapshots: has-flag@4.0.0: {} + has-flag@5.0.1: {} + has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 @@ -38864,6 +39181,10 @@ snapshots: dependencies: lru-cache: 6.0.0 + hosted-git-info@9.0.2: + dependencies: + lru-cache: 11.3.5 + hpagent@1.2.0: {} hsl-regex@1.0.0: {} @@ -39199,6 +39520,8 @@ snapshots: indent-string@5.0.0: {} + index-to-position@1.2.0: {} + indexes-of@1.0.1: {} indexof@0.0.1: {} @@ -39571,6 +39894,8 @@ snapshots: is-path-inside@3.0.3: {} + is-path-inside@4.0.0: {} + is-plain-obj@1.1.0: {} is-plain-obj@2.1.0: {} @@ -39789,6 +40114,12 @@ snapshots: editions: 1.3.4 textextensions: 2.6.0 + istextorbinary@9.5.0: + dependencies: + binaryextensions: 6.11.0 + editions: 6.22.0 + textextensions: 6.11.0 + iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -41265,6 +41596,8 @@ snapshots: lodash.some@4.6.0: {} + lodash.sortby@4.7.0: {} + lodash.template@4.5.0: dependencies: lodash._reinterpolate: 3.0.0 @@ -41283,6 +41616,8 @@ snapshots: lodash._baseiteratee: 4.7.0 lodash._baseuniq: 4.6.0 + lodash.uniqwith@4.5.0: {} + lodash.upperfirst@4.3.1: {} lodash@4.18.1: {} @@ -42529,6 +42864,12 @@ snapshots: semver: 7.7.4 validate-npm-package-license: 3.0.4 + normalize-package-data@8.0.0: + dependencies: + hosted-git-info: 9.0.2 + semver: 7.7.4 + validate-npm-package-license: 3.0.4 + normalize-path@2.1.1: dependencies: remove-trailing-separator: 1.1.0 @@ -43027,6 +43368,12 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.0 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse-ms@1.0.1: {} parse-ms@2.1.0: {} @@ -43255,6 +43602,8 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + pluralize@2.0.0: {} + pluralize@8.0.0: {} pngjs@6.0.0: {} @@ -44342,6 +44691,15 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 + rc-config-loader@4.1.4: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + js-yaml: 4.1.1 + json5: 2.2.3 + require-from-string: 2.0.2 + transitivePeerDependencies: + - supports-color + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -44540,6 +44898,14 @@ snapshots: read-pkg: 6.0.0 type-fest: 1.4.0 + read-pkg@10.1.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 8.0.0 + parse-json: 8.3.0 + type-fest: 5.5.0 + unicorn-magic: 0.4.0 + read-pkg@3.0.0: dependencies: load-json-file: 4.0.0 @@ -45232,6 +45598,19 @@ snapshots: ajv-formats: 2.1.1(ajv@8.18.0) ajv-keywords: 5.1.0(ajv@8.18.0) + secretlint@12.3.1: + dependencies: + '@secretlint/config-creator': 12.3.1 + '@secretlint/formatter': 12.3.1 + '@secretlint/node': 12.3.1 + '@secretlint/profiler': 12.3.1 + '@secretlint/resolver': 12.3.1 + debug: 4.4.3(supports-color@5.5.0) + globby: 16.2.0 + read-pkg: 10.1.0 + transitivePeerDependencies: + - supports-color + section-tests@1.3.1: dependencies: '@distributed-systems/callsite': 1.1.1 @@ -46094,6 +46473,10 @@ snapshots: '@tokenizer/token': 0.3.0 peek-readable: 4.1.0 + structured-source@4.0.0: + dependencies: + boundary: 2.0.0 + 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 @@ -46222,6 +46605,8 @@ snapshots: supports-color@1.2.0: {} + supports-color@10.2.2: {} + supports-color@2.0.0: {} supports-color@5.5.0: @@ -46241,6 +46626,11 @@ snapshots: has-flag: 4.0.0 supports-color: 7.2.0 + supports-hyperlinks@4.4.0: + dependencies: + has-flag: 5.0.1 + supports-color: 10.2.2 + supports-preserve-symlinks-flag@1.0.0: {} svg-country-flags@1.2.10: {} @@ -46456,6 +46846,11 @@ snapshots: mkdirp: 0.5.6 rimraf: 2.6.3 + terminal-link@5.0.0: + dependencies: + ansi-escapes: 7.3.0 + supports-hyperlinks: 4.4.0 + terser-webpack-plugin@1.4.6(webpack@4.47.0): dependencies: cacache: 12.0.4 @@ -46612,6 +47007,10 @@ snapshots: textextensions@2.6.0: {} + textextensions@6.11.0: + dependencies: + editions: 6.22.0 + thenby@1.3.4: {} thenify-all@1.6.0: @@ -47111,6 +47510,8 @@ snapshots: unicorn-magic@0.3.0: {} + unicorn-magic@0.4.0: {} + unidecode@0.1.8: {} unidecode@1.1.0: {} @@ -47366,6 +47767,8 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 + version-range@4.15.0: {} + vfile-location@2.0.6: {} vfile-message@2.0.4: