Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
81cb22e
🐛 Stopped automation runs when member unsubscribes (#29959)
EvanHahn Aug 17, 2026
5d50c27
Removed stale ignoreDeps from Renovate config (#30013)
9larsons Aug 17, 2026
aa25788
Renamed Mailgun provider ID database column (#30025)
EvanHahn Aug 17, 2026
66c0bf7
Updated metascraper + jsdom dependencies to latest (#30026)
acburdine Aug 17, 2026
696657a
TypeScriptified data generator "luck" utility (#30028)
EvanHahn Aug 17, 2026
45118e9
🐛 Fixed redirecting outbound requests crashing Ghost (#30027)
9larsons Aug 17, 2026
1637e97
🐛 Fixed periodic host limits in Admin (#29837)
betschki Aug 17, 2026
c0254ce
🎨 Changed the upgrade destination on host limits to be configurable (…
betschki Aug 17, 2026
a422393
TypeScriptify data generator table importer base class (#30029)
EvanHahn Aug 17, 2026
155709f
Updated nql/nql-lang deps to latest (#30032)
acburdine Aug 17, 2026
ac87f8e
Fixed stack overflow error in email batches data seeder (#30022)
EvanHahn Aug 17, 2026
3db4c02
Updated @tryghost/metrics to latest, dedupe framework versions (#30031)
acburdine Aug 17, 2026
dc96d09
Fixed stack overflow error in table importer (#30021)
EvanHahn Aug 17, 2026
fbd2b66
Tightened object lookup in data generator sorter (#30034)
EvanHahn Aug 17, 2026
b658b76
Update dependency parse-email-address to v0.0.4 (#30036)
EvanHahn Aug 17, 2026
4f028d8
Added index to prep for automation run analytics query (#29950)
EvanHahn Aug 17, 2026
accdee7
Update Docker image digests (#29990)
tryghost-renovate[bot] Aug 17, 2026
022293f
Added automations records to data generator (#30033)
EvanHahn Aug 17, 2026
ce08414
Added "last run created at" stat to automation browse endpoint (#29951)
EvanHahn Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/open-shirts-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tryghost/adapter-base-scheduling": patch
"@tryghost/adapter-base-sso": patch
---

Update framework dependencies
11 changes: 11 additions & 0 deletions .changeset/six-parents-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@tryghost/kg-default-nodes": minor
"@tryghost/kg-html-to-lexical": minor
"@tryghost/kg-lexical-html-renderer": minor
"@tryghost/kg-clean-basic-html": patch
"@tryghost/kg-default-cards": patch
"@tryghost/kg-unsplash-selector": patch
"@tryghost/koenig-lexical": patch
---

Update jsdom to 30 & node engines to match jsdom's
5 changes: 5 additions & 0 deletions .changeset/young-spiders-request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tryghost/adapter-base-scheduling": patch
---

Updated logging requests to avoid process crashes on redirects.
65 changes: 13 additions & 52 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -124,64 +124,25 @@
},
"ignoreDeps": [
// https://github.com/TryGhost/Ghost/commit/2b9e494dfcb95c40f596ccf54ec3151c25d53601
// `got` 10.x has a Node 10 bug that makes it pretty much unusable for now
"got",
// https://github.com/TryGhost/Ghost/commit/2b9e494dfcb95c40f596ccf54ec3151c25d53601
// `intl-messageformat` 6.0.0 introduced a breaking change in terms of
// escaping that would be pretty difficult to fix for now
// Theme i18n ({{t}} helper) is on intl-messageformat 5.x. 6.0.0 changed
// message escaping from `\` to ICU apostrophe quoting, which changes the
// output of existing theme locale strings; needs a deliberate migration.
"intl-messageformat",
// https://github.com/TryGhost/Ghost/commit/b2fa84c7ff9bf8e21b0791f268f57e92759a87b1
// no reason given
"moment",
// https://github.com/TryGhost/Ghost/pull/10672
// https://github.com/TryGhost/Ghost/issues/10870
"moment-timezone",
// https://github.com/TryGhost/Admin/pull/1111/files
// Ignored because of a mobiledoc-kit issue but that's now in koenig, can probably be cleaned up
"simple-dom",
// https://github.com/TryGhost/Admin/pull/1111/files
// https://github.com/TryGhost/Ghost/pull/10672
// These have been ignored since forever
"ember-drag-drop",
"normalize.css",
"validator",

// https://github.com/TryGhost/Ghost/commit/7ebf2891b7470a1c2ffeddefb2fe5e7a57319df3
// Changed how modules are loaded, caused a weird error during render
"@embroider/macros",

// https://github.com/TryGhost/Ghost/commit/a10ad3767f60ed2c8e56feb49e7bf83d9618b2ab
// Caused linespacing issues in the editor, but now it's used in different places
// So not sure if it's relevant - soon we will finish switching to react-codemirror
// codemirror 6 is a rewrite of the 5.x API. @tryghost/kg-simplemde (the
// Koenig markdown card) is built on 5.x and is not being ported. Also
// covers the `codemirror@<5.58.2` pnpm override key.
"codemirror",

// https://github.com/TryGhost/Ghost/commit/3236891b80988924fbbdb625d30cb64a7bf2afd1
// ember-cli-code-coverage@2.0.0 broke our code coverage
"ember-cli-code-coverage",
// https://github.com/TryGhost/Ghost/commit/1382e34e42a513c201cb957b7f843369a2ce1b63
// ember-cli-terser@4.0.2 has a regression that breaks our sourcemaps
"ember-cli-terser",

// https://linear.app/ghost/issue/PLA-56
// Deprecated (per-method lodash packages are EOL); last real release is
// 4.5.0 (2019). The flagged "4.18.0 [SECURITY]" bump does not exist, so
// Renovate can't remediate it. It's transitive-only — we never call
// `_.template` ourselves — and only reachable via frozen Ember build
// tooling (broccoli-templater, sourcemap-validator) plus old @tryghost/*
// packages, so it can't be removed from the tree here. Ignore it to stop
// the unactionable dashboard noise.
"lodash.template",

// https://linear.app/ghost/issue/PLA-78
// https://github.com/TryGhost/Ghost/pull/28180
// knex 2.5.x breaks migrations: 2.5.0's password-masking mutates the
// shared `config.get('database')` object, which we hand to BOTH knex()
// and knex-migrator. With core ahead of knex-migrator's pinned knex
// (2.4.2), the password is stripped before knex-migrator connects →
// "Access denied (using password: NO)". 2.x is EOL; the real move is
// knex 3.x, gated on publishing a knex-3 knex-migrator
// (TryGhost/knex-migrator#86, unreleased), bumped in lockstep with core.
// Ignore until we do that coordinated upgrade.
// ghost/core is pinned to knex 2.4.2 and knex-migrator's knex is pinned
// back to the same version via the `knex-migrator>knex` pnpm override
// (knex-migrator 5.4.x bundles knex 3.x). Migrations are built with
// core's query builder and executed by knex-migrator's, so the two must
// move together; 2.5.x also strips the password from the shared
// `config.get('database')` object before knex-migrator connects. Ignore
// until core moves to knex 3.x in one coordinated upgrade.
"knex",
// Same PLA-78 pin, but Renovate treats the pnpm override key
// `knex-migrator>knex` as its own dependency name, so the plain "knex"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tinybird.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
working-directory: ghost/core/core/server/data/tinybird
services:
tinybird:
image: tinybirdco/tinybird-local:latest@sha256:0865fb2f8aef0b8f0cd3ffe53a7a9832787b2d995fd0d34ddaa6563f1638feed
image: tinybirdco/tinybird-local:latest@sha256:cf9de0516700eea366161181e74cae943f1a7310e6f2cc4f5762cb1777ed3fb3
ports:
- 7181:7181
outputs:
Expand Down
12 changes: 12 additions & 0 deletions apps/admin-x-framework/src/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export type Config = {
billing?: {
enabled?: boolean
url?: string
// Destination for the upgrade button shown when a host limit is reached.
// Managed hosting providers can override it; falls back to Ghost's default.
upgradeUrl?: string // eg. '#/pro/billing/plans' or an absolute billing URL
// Copy and branding for the sidebar trial banner.
// Managed hosting providers can override these; each falls back to Ghost's default.
upgradeBanner?: {
Expand Down Expand Up @@ -148,3 +151,12 @@ export const hasSendingDomain = (config: Config) => {
export const sendingDomain = (config: Config) => {
return config?.hostSettings?.managedEmail?.sendingDomain;
};

export const DEFAULT_UPGRADE_ROUTE = '/pro';

// hostSettings stores an href; navigation takes a route, so a hash one drops its '#'
export const upgradeRoute = (config?: Config) => {
const configured = config?.hostSettings?.billing?.upgradeUrl;

return configured ? configured.replace(/^#/, '') : DEFAULT_UPGRADE_ROUTE;
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {centsToDollars} from '@tryghost/shade/app';
import {getPeriodText} from '@/shared/analytics/chart-helpers';
import {useAppContext} from '@tryghost/admin-x-framework';
import {useAnalytics} from '@/analytics/providers/analytics-context';
import {useAnalyticsData} from '@/shared/analytics/use-analytics-data';
import {upgradeRoute} from '@tryghost/admin-x-framework/api/config';
import {useLimiter} from '@/analytics/hooks/use-limiter';
import {useNavigate} from '@tryghost/admin-x-framework';

Expand Down Expand Up @@ -134,6 +136,7 @@ const OverviewKPIs:React.FC<OverviewKPIsProps> = ({
const navigate = useNavigate();
const {appSettings} = useAppContext();
const limiter = useLimiter();
const {config} = useAnalyticsData();
const isWebAnalyticsLimited = limiter.isLimited('limitAnalytics');

const areaChartClassName = '-mb-3 h-[10vw] max-h-[200px] min-h-[100px] hover:cursor-pointer!';
Expand Down Expand Up @@ -199,7 +202,7 @@ const OverviewKPIs:React.FC<OverviewKPIsProps> = ({
<CardContent className='flex h-full items-center justify-center p-6'>
<EmptyIndicator
actions={
<Button variant='outline' onClick={() => navigate('/pro', {crossApp: true})}>
<Button variant='outline' onClick={() => navigate(upgradeRoute(config), {crossApp: true})}>
Upgrade now
</Button>
}
Expand Down
19 changes: 16 additions & 3 deletions apps/admin/src/settings/advanced/integrations.acceptance.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ function apiKey(secret: string) {
};
}

function limitedConfig() {
const customIntegrationsLimit = {customIntegrations: {disabled: true, error: "Your plan does not support custom integrations"}};

function limitedConfig(upgradeUrl?: string) {
const response = configResponse();
response.config.labs = {...response.config.labs, transistor: true};
response.config.hostSettings = {limits: {customIntegrations: {disabled: true, error: "Your plan does not support custom integrations"}}};
response.config.hostSettings = upgradeUrl ?
{limits: customIntegrationsLimit, billing: {upgradeUrl}} :
{limits: customIntegrationsLimit};
return response;
}

Expand Down Expand Up @@ -279,7 +283,16 @@ describe("Advanced integrations", () => {
}

await section.getByTestId("zapier-integration").getByRole("button", {name: "Upgrade"}).click();
expect(JSON.parse(document.body.dataset.externalNavigate!)).toMatchObject({route: "pro"});
expect(JSON.parse(document.body.dataset.externalNavigate!)).toMatchObject({route: "/pro"});
});

it("sends the upgrade CTA to a host's own billing app when one is configured", async () => {
fakeSettingsScreens();
await renderAdminApp("/settings/integrations", {boot: {browseConfig: {response: limitedConfig("#/pro/billing/plans")}}});

const section = settingsScreen.section("integrations");
await section.getByTestId("zapier-integration").getByRole("button", {name: "Upgrade"}).click();
expect(JSON.parse(document.body.dataset.externalNavigate!)).toMatchObject({route: "/pro/billing/plans"});
});

it("saves FirstPromoter configuration and warns before discarding later changes", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {useConfirmation} from '@/settings/app/components/providers/confirmation-
import {useGlobalData} from '@/settings/app/components/providers/global-data-provider';
import {useHandleError} from '@tryghost/admin-x-framework/hooks';
import {useSettingsNavigation} from '@/settings/app/hooks/use-settings-navigation';
import {DEFAULT_UPGRADE_ROUTE} from '@tryghost/admin-x-framework/api/config';
import {useUpgradeRoute} from '@/settings/app/hooks/use-upgrade-route';
import {withErrorBoundary} from '@/settings/app/components/error-boundary';

interface IntegrationItemProps {
Expand All @@ -23,6 +25,7 @@ interface IntegrationItemProps {
onDelete?: () => void;
active?: boolean;
disabled?: boolean;
upgradeRoute?: string;
testId?: string;
custom?: boolean;
}
Expand All @@ -45,6 +48,7 @@ const IntegrationItem: React.FC<IntegrationItemProps> = ({
onDelete,
active,
disabled,
upgradeRoute = DEFAULT_UPGRADE_ROUTE,
testId,
custom = false
}) => {
Expand All @@ -55,7 +59,7 @@ const IntegrationItem: React.FC<IntegrationItemProps> = ({
e?.stopPropagation();

if (disabled) {
updateRoute({route: 'pro', isExternal: true});
updateRoute({route: upgradeRoute, isExternal: true});
} else {
action();
}
Expand Down Expand Up @@ -93,6 +97,7 @@ const IntegrationItem: React.FC<IntegrationItemProps> = ({

const BuiltInIntegrations: React.FC = () => {
const {config} = useGlobalData();
const upgradeRoute = useUpgradeRoute();
const {updateRoute} = useSettingsNavigation();

const openModal = (modal: string) => {
Expand Down Expand Up @@ -190,6 +195,7 @@ const BuiltInIntegrations: React.FC = () => {
icon={item.icon}
testId={item.testId}
title={item.title}
upgradeRoute={upgradeRoute}
/>
))}
</ActionList>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import {Field, FieldError, FieldGroup, FieldLabel, Input} from '@tryghost/shade/
import {HostLimitError, useLimiter} from '@/settings/app/hooks/use-limiter';
import {useConfirmation} from '@/settings/app/components/providers/confirmation-provider';
import {useSettingsNavigation} from '@/settings/app/hooks/use-settings-navigation';
import {useUpgradeRoute} from '@/settings/app/hooks/use-upgrade-route';
import {SettingsModal} from '@tryghost/shade/patterns';
import {useCreateIntegration} from '@tryghost/admin-x-framework/api/integrations';
import {useHandleError} from '@tryghost/admin-x-framework/hooks';

function AddIntegrationModal() {
const {updateRoute} = useSettingsNavigation();
const upgradeRoute = useUpgradeRoute();
const [name, setName] = useState('');
const [errors, setErrors] = useState({name: ''});
const {mutateAsync: createIntegration} = useCreateIntegration();
Expand All @@ -22,13 +24,13 @@ function AddIntegrationModal() {
if (error instanceof HostLimitError) {
showLimit({
prompt: error.message || `Your current plan doesn't support more custom integrations.`,
onOk: () => updateRoute({route: '/pro', isExternal: true})
onOk: () => updateRoute({route: upgradeRoute, isExternal: true})
});
updateRoute('integrations');
}
});
}
}, [limiter, showLimit, updateRoute]);
}, [limiter, showLimit, updateRoute, upgradeRoute]);

return <SettingsModal
okLabel='Add'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabe
import {HostLimitError, useLimiter} from '@/settings/app/hooks/use-limiter';
import {useConfirmation} from '@/settings/app/components/providers/confirmation-provider';
import {useSettingsNavigation} from '@/settings/app/hooks/use-settings-navigation';
import {useUpgradeRoute} from '@/settings/app/hooks/use-upgrade-route';
import {SettingsModal} from '@tryghost/shade/patterns';
import {formatNumber} from '@tryghost/shade/utils';
import {useAddNewsletter} from '@tryghost/admin-x-framework/api/newsletters';
Expand All @@ -12,6 +13,7 @@ import {useForm, useHandleError} from '@tryghost/admin-x-framework/hooks';

const AddNewsletterModal: React.FC = () => {
const {updateRoute} = useSettingsNavigation();
const upgradeRoute = useUpgradeRoute();
const returnRoute = useFeatureFlag('automations') ? 'emails' : 'newsletters';
const handleError = useHandleError();
const {showLimit} = useConfirmation();
Expand Down Expand Up @@ -77,11 +79,11 @@ const AddNewsletterModal: React.FC = () => {
if (limitError) {
showLimit({
prompt: limitError.message || `Your current plan doesn't support more newsletters.`,
onOk: () => updateRoute({route: '/pro', isExternal: true})
onOk: () => updateRoute({route: upgradeRoute, isExternal: true})
});
updateRoute(returnRoute);
}
}, [limitError, returnRoute, updateRoute, showLimit]);
}, [limitError, returnRoute, updateRoute, showLimit, upgradeRoute]);

if (isCheckingLimit || limitError) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {PreviewModalContent} from '@/settings/app/components/settings/preview-mo
import {useConfirmation} from '@/settings/app/components/providers/confirmation-provider';
import {useParams} from '@tryghost/admin-x-framework';
import {useSettingsNavigation} from '@/settings/app/hooks/use-settings-navigation';
import {useUpgradeRoute} from '@/settings/app/hooks/use-upgrade-route';
import {getSettingValue, getSettingValues} from '@tryghost/admin-x-framework/api/settings';
import {hasSendingDomain, isManagedEmail, sendingDomain} from '@tryghost/admin-x-framework/api/config';
import {renderReplyToEmail, renderSenderEmail} from '@/settings/app/utils/newsletter-emails';
Expand Down Expand Up @@ -97,6 +98,7 @@ const Sidebar: React.FC<{
}> = ({newsletter, onlyOne, updateNewsletter, validate, errors, clearError}) => {
type FontOption = {value: string; label: string; className?: string};
const {updateRoute} = useSettingsNavigation();
const upgradeRoute = useUpgradeRoute();
const {mutateAsync: editNewsletter} = useEditNewsletter();
const limiter = useLimiter();
const {settings, config, siteData} = useGlobalData();
Expand Down Expand Up @@ -177,7 +179,7 @@ const Sidebar: React.FC<{
if (error instanceof HostLimitError) {
showLimit({
prompt: error.message || `Your current plan doesn't support more newsletters.`,
onOk: () => updateRoute({route: '/pro', isExternal: true})
onOk: () => updateRoute({route: upgradeRoute, isExternal: true})
});
return;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {LucideIcon} from '@tryghost/shade/utils';
import {Pencil, Trash2} from 'lucide-react';
import {useLocation, useParams} from '@tryghost/admin-x-framework';
import {useSettingsNavigation} from '@/settings/app/hooks/use-settings-navigation';
import {useUpgradeRoute} from '@/settings/app/hooks/use-upgrade-route';
import {SOCIAL_PLATFORM_CONFIGS, SOCIAL_PLATFORM_KEYS, getSocialValidationError} from '@/settings/app/utils/social-urls/index';
import {SettingsModal} from '@tryghost/shade/patterns';
import {Text} from '@tryghost/shade/primitives';
Expand Down Expand Up @@ -75,6 +76,7 @@ export interface UserDetailProps {

const UserDetailModalContent: React.FC<{user: User; onDeletingUserChange: (isDeleting: boolean) => void}> = ({user, onDeletingUserChange}) => {
const {updateRoute} = useSettingsNavigation();
const upgradeRoute = useUpgradeRoute();
const location = useLocation();

const getTabFromPath = (path: string): string => {
Expand Down Expand Up @@ -174,7 +176,7 @@ const UserDetailModalContent: React.FC<{user: User; onDeletingUserChange: (isDel
showLimit({
formSheet: true,
prompt: error.message || `Your current plan doesn't support more users.`,
onOk: () => updateRoute({route: '/pro', isExternal: true})
onOk: () => updateRoute({route: upgradeRoute, isExternal: true})
});
return;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {HostLimitError, useLimiter} from '@/settings/app/hooks/use-limiter';
import {SettingGroupContent} from '@tryghost/shade/patterns';
import {getSettingValues, isSettingReadOnly} from '@tryghost/admin-x-framework/api/settings';
import {useSettingsNavigation} from '@/settings/app/hooks/use-settings-navigation';
import {useUpgradeRoute} from '@/settings/app/hooks/use-upgrade-route';
import {withErrorBoundary} from '@/settings/app/components/error-boundary';

const Analytics: React.FC<{ keywords: string[] }> = ({keywords}) => {
Expand All @@ -27,6 +28,7 @@ const Analytics: React.FC<{ keywords: string[] }> = ({keywords}) => {
const [isWebAnalyticsLimited, setIsWebAnalyticsLimited] = useState(false);
const limiter = useLimiter();
const {updateRoute} = useSettingsNavigation();
const upgradeRoute = useUpgradeRoute();

useEffect(() => {
if (limiter?.isLimited('limitAnalytics')) {
Expand Down Expand Up @@ -72,7 +74,7 @@ const Analytics: React.FC<{ keywords: string[] }> = ({keywords}) => {
<div className='mb-5 rounded-md border border-grey-200 bg-grey-50 px-4 py-2.5 dark:border-grey-900 dark:bg-grey-900'>
<span className='flex items-start gap-2'>
<span>
Web analytics is available on the Publisher plan and above. <span className='cursor-pointer text-green' onClick={() => updateRoute({route: '/pro', isExternal: true})}>Upgrade now &rarr;</span>
Web analytics is available on the Publisher plan and above. <span className='cursor-pointer text-green' onClick={() => updateRoute({route: upgradeRoute, isExternal: true})}>Upgrade now &rarr;</span>
</span>
</span>
</div>
Expand Down
Loading
Loading