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
9dda616
Updated migration checklist: you don't need to test in SQLite (#29948)
EvanHahn Aug 13, 2026
74eeda5
Tested email analytics database queries (#29890)
EvanHahn Aug 13, 2026
84acdc3
Updated actions/docker digests to latest (#29949)
acburdine Aug 13, 2026
aac8c2a
Added types for database connection object (#29891)
EvanHahn Aug 13, 2026
b23de25
Tested email analytics initializer (#29892)
EvanHahn Aug 13, 2026
c19b9a2
Update CSS preprocessors (#29478)
tryghost-renovate[bot] Aug 13, 2026
47018a3
Fixed renovate config issues (#29947)
acburdine Aug 13, 2026
375981f
Updated nx + other root dev dependencies (#29954)
acburdine Aug 13, 2026
ca8e43e
Update Docker image digests (#29590)
tryghost-renovate[bot] Aug 13, 2026
51aa95d
Updated vitest/types/scripts dependencies (#29956)
acburdine Aug 13, 2026
4f6a758
Removed jest dep from apps/activitypub (#29957)
acburdine Aug 13, 2026
011dd67
Updated more dev dependencies (#29960)
acburdine Aug 13, 2026
05447b2
Cleaned up remaining node-fetch/cross-fetch references (#29961)
acburdine Aug 13, 2026
120bbd1
Initialize email analytics Prometheus metrics in proper spot (#29893)
EvanHahn Aug 13, 2026
58af71d
🐛 Improved reliability of email analytics events (#29895)
EvanHahn Aug 13, 2026
1ad33ce
TypeScriptify email analytics service tests (#29923)
EvanHahn Aug 13, 2026
a415856
Updated email analytics service wrapper to always define sub-service …
EvanHahn Aug 13, 2026
f6d69ce
Migrated Codex documentation into the repo (#29958)
ErisDS Aug 13, 2026
089f7e8
TypeScriptify email analytics service wrapper (#29964)
EvanHahn Aug 13, 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
17 changes: 12 additions & 5 deletions .agents/skills/create-database-migration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,27 @@ description: Create a database migration to add a table, add columns to an exist

# Create Database Migration

Read the canonical human guidance in
[`docs/practices/database-migrations.md`](../../../docs/practices/database-migrations.md)
before making a migration. This skill is the executable checklist that
accompanies it.

## Instructions

1. Create a new, empty migration file: `cd ghost/core && pnpm migrate:create <kebab-case-slug>`. IMPORTANT: do not create the migration file manually; always use this script to create the initial empty migration file. The slug must be kebab-case (e.g. `add-column-to-posts`).
2. The above command will create a new directory in `ghost/core/core/server/data/migrations/versions` if needed, create the empty migration file with the appropriate name, and bump the core and admin package versions to RC if this is the first migration after a release.
3. Update the migration file with the changes you want to make in the database, following the existing patterns in the codebase. Where appropriate, prefer to use the utility functions in `ghost/core/core/server/data/migrations/utils/*`.
4. Update the schema definition file in `ghost/core/core/server/data/schema/schema.js`, and make sure it aligns with the latest changes from the migration.
5. Test the migration manually: `cd ghost/core && pnpm knex-migrator migrate --v {version directory} --force`
6. If adding or dropping a table, update `ghost/core/core/server/data/exporter/table-lists.js` as appropriate.
7. If adding or dropping a table, also add or remove the table name from the expected tables list in `ghost/core/test/integration/exporter/exporter.test.js`. This test has a hardcoded alphabetically-sorted array of all database tables — it runs in CI integration tests (not unit tests) and will fail if the new table is missing.
8. Run the schema integrity test, and update the hash: `cd ghost/core && pnpm test:single test/unit/server/data/schema/integrity.test.js`
9. Run unit tests in Ghost core, and iterate until they pass: `cd ghost/core && pnpm test:unit`
6. Roll the migration back to test `down()`: `cd ghost/core && pnpm knex-migrator rollback --v {previous version} --force`, then migrate forward again.
7. Run the migration integration test, which covers initialization, rollback, forward migration, and idempotency: `cd ghost/core && pnpm test:single test/integration/migrations/migration.test.js`. Migrations must pass the database-backed suites against both MySQL and SQLite.
8. If adding or dropping a table, update `ghost/core/core/server/data/exporter/table-lists.js` as appropriate. The consistency assertion in `ghost/core/test/unit/server/data/exporter/index.test.js` checks that every schema table is classified in the exporter lists.
9. Run the focused exporter unit test when the table lists change: `cd ghost/core && pnpm test:single test/unit/server/data/exporter/index.test.js`.
10. Run the schema integrity test, and update the hash: `cd ghost/core && pnpm test:single test/unit/server/data/schema/integrity.test.js`
11. Run unit tests in Ghost core, and iterate until they pass: `cd ghost/core && pnpm test:unit`

## Examples
See [examples.md](examples.md) for example migrations.

## Rules
See [rules.md](rules.md) for rules that should always be followed when creating database migrations.
See [rules.md](rules.md) for rules that should always be followed when creating database migrations.
20 changes: 20 additions & 0 deletions .changeset/breezy-olives-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@tryghost/kg-default-nodes": none
"@tryghost/adapter-base-cache": none
"@tryghost/adapter-base-scheduling": none
"@tryghost/adapter-base-sso": none
"@tryghost/kg-card-factory": none
"@tryghost/kg-clean-basic-html": none
"@tryghost/kg-converters": none
"@tryghost/kg-default-cards": none
"@tryghost/kg-default-transforms": none
"@tryghost/kg-html-to-lexical": none
"@tryghost/kg-lexical-html-renderer": none
"@tryghost/kg-markdown-html-renderer": none
"@tryghost/kg-unsplash-selector": none
"@tryghost/kg-utils": none
"@tryghost/koenig-lexical": none
"ghost-storage-base": none
---

Dev Dependency Updates
16 changes: 16 additions & 0 deletions .changeset/eight-guests-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@tryghost/kg-default-nodes": none
"@tryghost/koenig-lexical": none
"@tryghost/kg-card-factory": none
"@tryghost/kg-clean-basic-html": none
"@tryghost/kg-converters": none
"@tryghost/kg-default-cards": none
"@tryghost/kg-default-transforms": none
"@tryghost/kg-html-to-lexical": none
"@tryghost/kg-lexical-html-renderer": none
"@tryghost/kg-markdown-html-renderer": none
"@tryghost/kg-unsplash-selector": none
"@tryghost/kg-utils": none
---

Update dev deps
6 changes: 6 additions & 0 deletions .changeset/petite-schools-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tryghost/koenig-lexical": patch
"@tryghost/kg-unsplash-selector": patch
---

Updated dependencies
2 changes: 1 addition & 1 deletion .github/actions/load-docker-image/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ runs:

- name: Log in to GitHub Container Registry
if: inputs.use-artifact == 'false'
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/report-boot-benchmark/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ runs:
# means summary-only, so callers can use this action without publishing.
- name: Publish to benchmark series
if: github.event_name != 'pull_request' && inputs.github-token != ''
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with:
name: ${{ inputs.series }}
tool: 'customSmallerIsBetter'
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-playwright/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ runs:

- name: Check if Playwright browser is cached
id: playwright-cache
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-Playwright-${{ steps.playwright-version.outputs.version }}
Expand Down
54 changes: 39 additions & 15 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
// Track `# renovate:`-annotated `ARG/ENV *_VERSION` pins in Dockerfiles,
// for tool versions that no native manager sees (e.g. the Tinybird CLI
// installed via `uv tool install` in docker/tb-cli/Dockerfile).
"customManagers:dockerfileVersions"
"customManagers:dockerfileVersions",
// Same, for `# renovate:`-annotated `env:` version pins in workflows
// (the Tinybird CLI in .github/workflows/tinybird.yml).
"customManagers:githubActionsVersions"
],
// Keep Renovate's own concurrency guardrails in place. The shared preset
// extends :disableRateLimiting (prConcurrentLimit: 0, prHourlyLimit: 0),
Expand Down Expand Up @@ -42,13 +45,17 @@
// We have to disable platform based automerge (forcing renovate to do it manually)
// as otherwise renovate wont follow our schedule
"platformAutomerge": false,
// Keep every open Renovate branch current, including updates that require
// human review. The shared preset uses `rebaseWhen: automerging`, which
// Renovate resolves to `never` whenever a package rule disables automerge.
// Those PRs then conflict with main indefinitely and occupy the open-PR cap
// until somebody manually requests a rebase. Runner scheduling below still
// limits when the resulting branch updates and CI runs can happen.
"rebaseWhen": "behind-base-branch",
// Branch protection does not require up-to-date branches (the required
// status checks ruleset has strict: false), so a green-but-behind branch
// is still mergeable. Rebasing on every `main` commit therefore buys
// nothing and costs a force-push plus a full CI re-run per branch, which
// is what stopped branches from ever being green *and* current inside an
// automerge window. "conflicted" rebases only when a branch genuinely
// conflicts — in practice whenever another Renovate PR lands a
// pnpm-lock.yaml change, which is exactly when it's needed. It also keeps
// needs:review PRs current, unlike the preset's "automerging", which
// Renovate resolves to "never" wherever a package rule disables automerge.
"rebaseWhen": "conflicted",
// Soak every dependency update for 72 hours before opening a PR. This guards
// against compromised publishes (malicious version yanked within a few hours)
// and against unstable releases that get hotfixed shortly after publish.
Expand Down Expand Up @@ -82,24 +89,24 @@
// self-hosted workflow that means a CI-storm of force-pushes across all
// open Renovate PRs during the workday. Setting `updateNotScheduled:
// false` keeps existing branch maintenance inside the same windows.
// Existing branches can still be maintained outside the normal creation
// schedule when the workflow switches Renovate into cap-reached mode.
"updateNotScheduled": false,
"schedule": [
// Run all weekend
"* * * * 0,6",
// Run on weekday evenings
"* 23 * * 1-5",
// Run on early weekday mornings (previous day 23:00 is already
// covered by the evening/weekend blocks above)
"* 0-4 * * 1-6"
// covered by the evening/weekend blocks above). Extends to 05:59 to
// absorb GitHub Actions scheduler drift — delayed ticks were landing
// outside the window and doing nothing.
"* 0-5 * * 1-6"
],
"automergeSchedule": [
// Allow automerge all weekend
"* * * * 0,6",
// Allow automerge overnight on weekday evenings (11pm-4:59am UTC)
// Allow automerge overnight on weekday evenings (11pm-5:59am UTC)
"* 23 * * 1-5",
"* 0-4 * * 1-6"
"* 0-5 * * 1-6"
],
// Vulnerability alerts normally bypass Renovate's PR concurrency, hourly
// PR, and schedule limits. Let them keep doing that so security fixes can
Expand All @@ -108,7 +115,10 @@
"vulnerabilityAlerts": {
"schedule": ["at any time"],
"minimumReleaseAge": "3 days",
"rebaseWhen": "behind-base-branch",
// Same reasoning as the global `rebaseWhen` above: branches don't have
// to be current to merge, so rebasing a security PR on every `main`
// commit only delays the fix behind another CI cycle.
"rebaseWhen": "conflicted",
"dependencyDashboardApproval": false,
"labels": ["dependencies", "security"]
},
Expand Down Expand Up @@ -303,6 +313,20 @@
"allowedVersions": "<3.14"
},

// Stay on the MySQL 8 line — that's what Ghost supports and what CI and
// local dev run against. Capping (rather than disabling) keeps 8.x
// patches and digest re-pins flowing.
{
"description": "Cap MySQL at the 8.x line",
"matchDatasources": [
"docker"
],
"matchPackageNames": [
"mysql"
],
"allowedVersions": "<9"
},

// Keep `@types/*` aligned with the runtime major they describe. Type defs
// for a different major than what actually runs are silently wrong at best
// (e.g. @types/express 5 vs Express 4) and build-breaking at worst
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ How we write GitHub Actions workflows safely. Follow these when adding or editin
- **Pin every third-party action to a full commit SHA**, with the version as a trailing comment:

```yaml
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
```

A tag or branch ref can be re-pointed at malicious code; a SHA cannot.
Expand Down
Loading
Loading