Skip to content
This repository was archived by the owner on Mar 21, 2026. It is now read-only.

Upstream2#12

Open
ngeorger wants to merge 229 commits intomainfrom
upstream2
Open

Upstream2#12
ngeorger wants to merge 229 commits intomainfrom
upstream2

Conversation

@ngeorger
Copy link
Member

No description provided.

renovate bot and others added 30 commits October 15, 2025 10:15
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…Ghost#1378)

ref TryGhost#1324

Apparently the previous fix did not work, but it's not totally clear why. There
seems to be an issue using `always()` and `failure()` together.

According to the official GitHub docs, `failure()` should return true
`when any ancestor job fails,` but in practice (as documented in
https://github.com/orgs/community/discussions/80788),
it always returns false even when dependent jobs fail.

Have changed condition to explicitly check on the results of the previous jobs
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
closes https://linear.app/ghost/issue/PROD-2665

- Context: ActivityPub uses identity tokens provided by Ghost to authenticate and authorize requests. Identity tokens are JWT signed with RS256 and verifiable by a public key. Ghost exposes the public key as a JSON Web Key Set (JWKS) on <site_url>/ghost/.well-known/jwks.json. To avoid fetching the public key on each ActivityPub request, we cache it in Redis.

- Problem: we currently don't have any invalidation mechanism for the cached public key. If the key is rotated after e.g. a site migration, users experience a HTTP 403 authorization error and are unable to access the API/use the app

- Solution: if the JWT verification fails against a cached public key, we now retry by fetching a new key from Ghost and save the new key into the cache

---------

Co-authored-by: Sag <guptazy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ref https://linear.app/ghost/issue/BER-2908/add-dummy-api-endpoint-to-start-vertical-integration

- in the context of introducing the global feed, extracted the mapping from a feed result to a post DTO, so that it can reused
ref https://linear.app/ghost/issue/BER-2911

Updated the `post.created` event handler so that newly created posts from
Ghost publishers get added to a "global feed". This implementation is a
prototype to get a feel for how this feature would work in the client
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
no ref

Since we upgrade to the latest version of Biome we have been seeing lint
warnings being reported on the usage of `Number.parseInt` as a new rule was
added as part of the `recommended` ruleset
no ref

Removed some of the vendor specific agent files now that `agents.md` seems to
be the most widely supported (see https://agents.md/)
closes https://linear.app/ghost/issue/BER-2908

- in this first slice, the global feed returns all long-form posts (Articles) from all users, sorted by reverse-chron
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…st#1398)

no refs

When a new site is created it triggers a follow request to the Ghost Explore
mastodon account which ends up making 2 outbound requests (webfinger lookup +
follow activity). We do not want to do these live requests whilst testing so
we have stubbed them out using wiremock
ref https://linear.app/ghost/issue/BER-2413

Refactored the follow flow so that follow / unfollow for internal accounts
does not get federated as we can handle all of the operations required without
needing to out to the network
…flicts (TryGhost#1406)

no issue

- used port 444 for caddy-testing instead of the default HTTPS port (443), to avoid conflicts during local dev
…st#1405)

ref https://linear.app/ghost/issue/BER-2935

- this is the second iteration of the global feed experiment and the code is not final
- used integration tests to test the new behaviour and removed cucumber tests for now, as we're going to either drop the experiment or re-write the logic if we expand further on the experiment
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ref https://linear.app/ghost/issue/BER-2904

- The current code did an N+1 query, for each follower it queried the
database. This generated high load on the database when requests per
second spiked.
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ref https://linear.app/ghost/issue/BER-2943

- added new API endpoint: GET /feed/discover/:slug that returns a feed of posts on a given topic, sorted by recently published at
- this will replace the current GET /feed/global endpoint and related logic (a follow-up PR will clean up that code)
closes https://linear.app/ghost/issue/BER-2948

- The team uses Sentry and this is costing us 1,000$ a month.
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
renovate bot and others added 28 commits February 23, 2026 15:08
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…st#1617)

ref https://linear.app/ghost/issue/BER-3241

- We log the 410 and that should be enough to determine the request
failed.
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Refactor `PostCreatedEvent` to implement `SerializableEvent` to comply with ADR-0011 for serializable domain events.

Linear Issue: [BER-3174](https://linear.app/ghost/issue/BER-3174/refactor-postcreatedevent-to-implement-serializableevent-adr-0011)

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
no issue

- The `yarn dev` command only runs the ActivityPub service (without nginx), versus `yarn dev:standalone` runs ActivityPub + nginx
- As Ghost now runs a nginx service in development, we don't need to run it in ActivityPub
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
no ref

When setting up the app from fresh via `yarn dev` (no previous docker images,
no built `dist` dir), it was possible for an issue to occur where the container
tries to load `app.ts` before it is built, resulting in the error:

```
activitypub-1  | yarn run v1.22.22
activitypub-1  | $ concurrently "yarn build --watch" "node --inspect=0.0.0.0:9229 --watch dist/app.js"
activitypub-1  | [1] Debugger listening on ws://0.0.0.0:9229/99b125ec-4ba2-4fe8-9c14-e94ffa86b922
activitypub-1  | [1] For help, see: https://nodejs.org/en/docs/inspector
activitypub-1  | [1] node:internal/modules/cjs/loader:1386
activitypub-1  | [1]   throw err;
activitypub-1  | [1]   ^
activitypub-1  | [1]
activitypub-1  | [1] Error: Cannot find module '/opt/activitypub/dist/app.js'
activitypub-1  | [1]     at Function._resolveFilename (node:internal/modules/cjs/loader:1383:15)
activitypub-1  | [1]     at defaultResolveImpl (node:internal/modules/cjs/loader:1025:19)
activitypub-1  | [1]     at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1030:22)
activitypub-1  | [1]     at Function._load (node:internal/modules/cjs/loader:1192:37)
activitypub-1  | [1]     at TracingChannel.traceSync (node:diagnostics_channel:328:14)
activitypub-1  | [1]     at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
activitypub-1  | [1]     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
activitypub-1  | [1]     at node:internal/main/run_main_module:36:49 {
activitypub-1  | [1]   code: 'MODULE_NOT_FOUND',
activitypub-1  | [1]   requireStack: []
activitypub-1  | [1] }
activitypub-1  | [1]
activitypub-1  | [1] Node.js v22.22.0
activitypub-1  | [1] Failed running 'dist/app.js'. Waiting for file changes before restarting...
activitypub-1  | [1] node --inspect=0.0.0.0:9229 --watch dist/app.js exited with code 0
activitypub-1  | [0] $ esbuild src/app.ts --sourcemap --platform=neutral --bundle --packages=external --outfile=dist/app.js --watch
activitypub-1  | [0] [watch] build finished, watching for changes...
```

This changeset fixes the issue + also updates the debugger setup so that
sourcemaps are correctly loaded and debugging can be easily stared from vscode
flavoured IDE's
…1636)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Refactor PostDeletedEvent to implement SerializableEvent and use primitive data to comply with ADR-0011 and ensure event data persistence after post deletion.

The PostDeletedEvent previously contained the full Post entity, which is problematic for deletion events as the post may no longer exist in the database when the event is processed. This change ensures all necessary data for federation and other consumers is explicitly stored as primitive, serializable fields within the event itself.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…1640)

ref https://linear.app/ghost/issue/BER-3163

Misc clean up from https://linear.app/ghost/issue/BER-3163:

- add missing account.updated test
- clean up event name resolution
- clean up event tests
- reorder getName location
- cleanup event registration
if: steps.changes.outputs.populate-explore-json == 'true'
uses: google-github-actions/auth@v2
if: steps.changes.outputs.reconcile-account-topics == 'true'
uses: google-github-actions/auth@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Build' step
Uses Step: gcp-auth
uses 'google-github-actions/auth' with ref 'v3', not a pinned commit hash
- name: Authenticate with GCP
if: ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
uses: google-github-actions/auth@v2
uses: google-github-actions/auth@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Deploy PR' step
Uses Step
uses 'google-github-actions/auth' with ref 'v3', not a pinned commit hash
- name: Deploy Migrations to Cloud Run
if: ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
uses: google-github-actions/deploy-cloudrun@v2
uses: google-github-actions/deploy-cloudrun@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Deploy PR' step
Uses Step
uses 'google-github-actions/deploy-cloudrun' with ref 'v3', not a pinned commit hash
steps:
- name: Authenticate with GCP
uses: google-github-actions/auth@v2
uses: google-github-actions/auth@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Deploy' step
Uses Step
uses 'google-github-actions/auth' with ref 'v3', not a pinned commit hash
- name: Deploy Migrations to Cloud Run
if: ${{ matrix.region == 'europe-west4' }}
uses: google-github-actions/deploy-cloudrun@v2
uses: google-github-actions/deploy-cloudrun@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Deploy' step
Uses Step
uses 'google-github-actions/deploy-cloudrun' with ref 'v3', not a pinned commit hash

- name: "Authenticate with GCP"
uses: google-github-actions/auth@v2
uses: google-github-actions/auth@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Ephemeral Staging Tear Down' step
Uses Step
uses 'google-github-actions/auth' with ref 'v3', not a pinned commit hash
- name: "Authenticate with GCP"
if: ${{ steps.check-closed-prs.outputs.destroy_prs != '' }}
uses: google-github-actions/auth@v2
uses: google-github-actions/auth@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Ephemeral Staging Tear Down' step
Uses Step
uses 'google-github-actions/auth' with ref 'v3', not a pinned commit hash

- name: Authenticate with GCP
uses: google-github-actions/auth@v2
uses: google-github-actions/auth@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Test' step
Uses Step: gcp-auth
uses 'google-github-actions/auth' with ref 'v3', not a pinned commit hash
Comment on lines +14 to +41
name: Vitest
runs-on: ubuntu-latest
if: ${{ !inputs.skip-tests }}
steps:
- name: Check skip-tests
id: check-skip-tests
run: echo "skip_tests=${{ inputs.skip-tests }}" >> "$GITHUB_OUTPUT"

- name: Checkout
if: steps.check-skip-tests.outputs.skip_tests != 'true'
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Download ActivityPub image
if: steps.check-skip-tests.outputs.skip_tests != 'true'
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: activitypub-amd64
path: /tmp

- name: Download Migrations image
if: steps.check-skip-tests.outputs.skip_tests != 'true'
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: activitypub-migrations-amd64
path: /tmp

- name: Load Docker images
if: steps.check-skip-tests.outputs.skip_tests != 'true'
run: |
docker load < /tmp/activitypub-amd64.tar
docker load < /tmp/activitypub-migrations-amd64.tar

- name: Run Tests
if: steps.check-skip-tests.outputs.skip_tests != 'true'
run: yarn test
run: docker compose run --rm migrate-testing up && yarn test:all

e2e:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +42 to +67
name: E2E
runs-on: ubuntu-latest
if: ${{ !inputs.skip-tests }}
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Download ActivityPub image
uses: actions/download-artifact@v7
with:
name: activitypub-amd64
path: /tmp

- name: Download Migrations image
uses: actions/download-artifact@v7
with:
name: activitypub-migrations-amd64
path: /tmp

- name: Load Docker images
run: |
docker load < /tmp/activitypub-amd64.tar
docker load < /tmp/activitypub-migrations-amd64.tar

- name: Run E2E Tests
run: yarn test:cucumber

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
@ngeorger ngeorger marked this pull request as ready for review March 21, 2026 16:34
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants