Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
197 changes: 196 additions & 1 deletion .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,212 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
reviews:
profile: quiet
request_changes_workflow: false
review_details: true
review_status: true
review_progress: true
high_level_summary: false
collapse_walkthrough: false
changed_files_summary: false
sequence_diagrams: false
estimate_code_review_effort: false
poem: false
auto_review:
enabled: true
drafts: false
ignore_usernames:
- tryghost-renovate[bot]
- app/tryghost-renovate
- dependabot[bot]
path_filters:
- "!**/dist/**"
- "!**/build/**"
- "!**/built/**"
- "!**/umd/**"
- "!**/coverage/**"
pre_merge_checks:
docstrings:
mode: "off"
custom_checks:
- name: "Type-safe boundaries"
mode: "warning"
instructions: |
Fail only if the PR:
- consumes boundary data (HTTP input, external API/SDK responses, env/config,
DB/filesystem reads, queue/webhook/event payloads) without validating it
first — Zod by default, another format only where an external contract
requires it; or
- introduces `any`, unchecked `as`, `@ts-nocheck`, or `@ts-ignore` to bypass
typing boundary data; or
- hand-writes a type duplicating a shape a Zod schema describes (use z.infer).
Never fail for: internal function/module calls (no runtime validation needed),
pre-existing JS files touched incidentally, tests, scripts, or config files.
- name: "New files are TypeScript"
mode: "error"
instructions: |
Fail if the PR adds a new .js/.jsx/.cjs/.mjs source file, unless it is: a DB
migration (ghost/core/core/server/data/migrations/), under apps/ember-admin/,
a tool/config file, under scripts/ or docker/, or generated/vendored code.
Modifying pre-existing JS files never fails this check.
path_instructions:
- path: "**/*"
instructions: |
Prioritise concrete correctness, security, data-integrity, compatibility,
and regression risks. Explain the failure mode and point to the affected
code. Do not report formatting, naming, import ordering, type errors, or
other findings already owned by configured static tools or failing GitHub
checks. Do not request speculative abstractions, broad refactors, generic
documentation, or tests unrelated to changed behaviour. Treat nearby
AGENTS.md files and mapped codebase documentation as authoritative; do not
enforce proposals, plans, or historical guidance as current policy.
- path: "**/*.{ts,tsx,mts,cts}"
instructions: |
Review lens: "where does this data become trusted?"
- Boundary data (HTTP input, external API/SDK responses, env/config,
DB/filesystem reads, queue/webhook/event payloads) is `unknown` until
validated — Zod by default.
- Infer boundary types via z.infer/z.input; flag handwritten duplicates.
- Flag `any`, unchecked `as` on boundary data, `@ts-nocheck`, and unexplained
`@ts-ignore`/`@ts-expect-error`.
- Validated data stays trusted: don't request Zod on internal calls, and flag
redundant re-validation.
- ghost/core golden path: schema.ts owns Zod schemas + inferred types, with
codec/serializer modules at the edges (see core/server/services/gift-links).
- Looser typing in tests is fine unless it hides a real defect.
- path: "**/*.{js,jsx,cjs,mjs}"
instructions: |
New source files must be TypeScript: flag new JS files as a required change
unless exempt (DB migrations, apps/ember-admin/, tool/config files, scripts/,
docker/, generated code).
Never request conversion of pre-existing JS files. If the PR substantially
reworks one (rewritten logic or significant new functions — not renames or
small fixes), you may leave ONE optional, non-blocking note for the whole PR
that those files are cheap TS-conversion candidates; skip minor changes and
exempt areas.
If the PR adds or changes a runtime boundary (parsing HTTP input, JSON, config,
external responses), suggest validating it — ideally with TS + Zod.
- path: "ghost/core/core/server/api/**"
instructions: |
Review API contract semantics: authentication and permissions, validation at
untrusted boundaries, writable-field allowlists, accidental response-data
exposure, stable error codes/statuses, pagination/filter consistency, cache
invalidation, and compatibility with existing clients. Require tests only for
changed behaviour or a credible regression path. Do not repeat endpoint
complexity, filenames, typing, or other ESLint/schema failures.
- path: "ghost/core/core/server/services/**"
instructions: |
Review new or changed service boundaries for explicit dependency ownership,
deterministic/idempotent initialisation, boot ordering, transaction and event
semantics, cache coherence, and restart/multi-instance safety. New standalone
services default to TypeScript; extending an existing JavaScript service is an
accepted exception. Do not enforce unapproved repository, ORM, or dependency-
injection proposals as current architecture.
- path: "ghost/core/core/server/data/{migrations,schema}/**"
instructions: |
Review migration safety beyond lint: schema and migration parity, existing-data
shape and volume, deploy/rollback compatibility, transaction and locking risk,
idempotency, export/integrity updates, and preservation of constraints/defaults.
Do not duplicate migration filename, loop, schema-field, or integrity-check CI.
- path: "packages/**"
instructions: |
Review package boundaries and production consumption: minimal explicit exports,
declared runtime dependencies, source-condition versus built-output parity,
copied runtime assets, ESM/NodeNext compatibility, and consumer-facing release
impact. Respect ghostPackage migration/exempt metadata and public/browser/test-
only exceptions. Do not repeat fields enforced by lint:packages or changeset CI.
- path: "apps/{admin,activitypub,admin-x-framework,shade}/**/*.{ts,tsx}"
instructions: |
Review Admin UI for existing Shade reuse, correct component layer, semantic
tokens, accessible interaction states, and whole-sentence translations. New UI
that depends on backend settings, endpoints, or config must feature-detect old
backend support and cover the not-yet-deployed backend case. Do not apply these
rules to independent public UMD apps. Do not repeat ESLint/Tailwind findings.
- path: "apps/{portal,comments-ui,signup-form,sodo-search,announcement-bar}/**/*.{js,jsx,ts,tsx}"
instructions: |
These are independent public UMD/CDN surfaces, not embedded Shade apps. Review
backwards-compatible browser behaviour, bundle/runtime assumptions, accessible
recovery states, namespace-correct whole-sentence translations, and safe
handling of server-provided data. Do not request Shade adoption or Admin-only
Tailwind conventions.
- path: "e2e/tests/**/*.ts"
instructions: |
Review semantic E2E quality that static checks miss: test the user-visible
integration at the lowest useful layer; prefer web-first assertions and
semantic locators; keep reusable interactions in page objects and assertions in
tests; avoid hard waits and networkidle; use factories and preserve isolation.
Per-file environment reuse is the default, so request per-test isolation only
for state-heavy cases that genuinely need it. A direct semantic locator is fine
for a small one-off assertion. Do not repeat Playwright ESLint or CI failures.
- path: "e2e/helpers/**/*.ts"
instructions: |
Review fixture/page-object lifecycle, concurrency, reset timing, reusable
readiness guards, and stable public locators. Page objects may use necessary
structural selectors for iframe/editor/theme internals but must not contain
business assertions. Preserve the documented per-file/per-test isolation model.
- path: "**/*{.,-}{test,spec}.{js,jsx,ts,tsx}"
instructions: |
Review whether tests prove changed behaviour, meaningful error/edge paths, and
externally observable contracts without coupling to implementation details.
Prefer the lowest useful test layer. Do not demand broad E2E coverage for
isolated logic or repeat test-run failures already visible in GitHub checks.
- path: "docs/**/*.md"
instructions: |
Check technical claims, paths, commands, and declared authority/status against
the current repository. Flag contradictions and stale instructions with a
concrete source of truth. Do not demand generic tutorial expansion or enforce
proposal language on production code.
tools:
eslint:
enabled: true
oxc:
enabled: true
stylelint:
enabled: true
emberTemplateLint:
enabled: true
actionlint:
enabled: true
zizmor:
enabled: true
yamllint:
enabled: true
shellcheck:
enabled: true
opengrep:
enabled: true
gitleaks:
enabled: true
trufflehog:
enabled: true
osvScanner:
enabled: true
github-checks:
enabled: true

knowledge_base:
code_guidelines:
enabled: true
filePatterns:
- files: "docs/practices/api-design.md"
applyTo: "ghost/core/core/server/api/**,packages/admin-api-schema/**"
- files: "docs/practices/database-migrations.md"
applyTo: "ghost/core/core/server/data/migrations/**,ghost/core/core/server/data/schema/**"
- files: "docs/practices/error-handling.md"
applyTo: "ghost/core/core/server/**,apps/**/*.{js,jsx,ts,tsx}"
- files: "docs/practices/internationalization.md"
applyTo: "apps/**/*.{js,jsx,ts,tsx},packages/i18n/**"
- files: "docs/contributing/testing.md"
applyTo: "**/{test,tests}/**,**/*{.,-}{test,spec}.{js,jsx,ts,tsx}"
- files: "docs/codebase/monorepo-structure.md"
applyTo: "package.json,pnpm-workspace.yaml,nx.json,apps/**,packages/**,ghost/core/**,koenig/**"
- files: "docs/codebase/configuration.md"
applyTo: "ghost/core/core/shared/config/**,ghost/core/config*.json*"
- files: "docs/codebase/internal-caching.md"
applyTo: "ghost/core/core/server/adapters/cache/**,ghost/core/core/server/adapters/lib/redis/**,ghost/core/core/server/**/*cache*.{js,ts},ghost/core/core/shared/config/**,packages/adapters/cache-base/**"
- files: "docs/codebase/jobs.md"
applyTo: "ghost/core/core/server/services/**"
- files: "packages/README.md"
applyTo: "packages/**"
- files: "apps/shade/AGENTS.md"
applyTo: "apps/admin/**,apps/activitypub/**,apps/admin-x-framework/**,apps/shade/**"
- files: "e2e/README.md,e2e/AGENTS.md"
applyTo: "e2e/**"
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,9 @@ jobs:
- name: Build TS code
run: pnpm nx run-many -t build:tsc

- name: Build assets
run: pnpm --filter ghost run build:assets

- name: Run hyperfine on boot
working-directory: ghost/core
run: hyperfine --show-output --warmup 3 'GHOST_CI_SHUTDOWN_AFTER_BOOT=1 node index.js' --export-json boot-perf.json
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ test/functional/*.png
/ghost/core/core/frontend/public/member-attribution.min.js
/ghost/core/core/frontend/public/ghost-stats.min.js
/ghost/core/core/frontend/public/private.min.js
/ghost/core/core/frontend/public/cards.manifest.json
# Caddyfile - for local development with ssl + caddy
Caddyfile
!docker/dev-gateway/Caddyfile
Expand Down
16 changes: 16 additions & 0 deletions .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config": {
"default": false,
"MD011": true,
"MD018": true,
"MD019": true,
"MD020": true,
"MD021": true,
"MD037": true,
"MD038": true,
"MD039": true,
"MD051": true,
"MD052": true,
"MD056": true
}
}
29 changes: 20 additions & 9 deletions ghost/core/core/frontend/meta/asset-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const urlUtils = require('../../shared/url-utils').default;
const {SafeString} = require('../services/handlebars');
const assetHash = require('../services/asset-hash');
const themeEngine = require('../services/theme-engine');
const {cardAssets} = require('../services/assets-minification');

/**
* Serve either uploaded favicon or default
Expand Down Expand Up @@ -121,9 +122,12 @@ function getAssetUrl(assetPath, hasMinFile) {
return getFaviconUrl();
}

// Determine asset type
const isPublicAsset = assetPath.match(/^public\//);
const isThemeAsset = !isPublicAsset && !assetPath.match(/^asset/);
// Determine asset type. Anything that isn't a public asset is served out of the
// active theme's assets/ directory — but a caller may already have spelled that
// prefix themselves, in which case we must not add it a second time.
const isPublicAsset = !!assetPath.match(/^public\//);
const isThemeAsset = !isPublicAsset;
const hasAssetsPrefix = isThemeAsset && !!assetPath.match(/^asset/);

// CASE: Build the output URL
// If assetCdnUrl is configured, use it as the base (produces an absolute URL).
Expand All @@ -134,7 +138,7 @@ function getAssetUrl(assetPath, hasMinFile) {
: urlUtils.urlJoin(urlUtils.getSubdir(), '/');

// Optionally add /assets/
if (isThemeAsset) {
if (isThemeAsset && !hasAssetsPrefix) {
output = urlUtils.urlJoin(output, 'assets/');
}

Expand All @@ -149,12 +153,19 @@ function getAssetUrl(assetPath, hasMinFile) {
// Get the appropriate hash for this asset (ignore URL anchor)
const hashPath = assetPath.includes('#') ? assetPath.slice(0, assetPath.indexOf('#')) : assetPath;
let hash;
// Use file-based SHA256 hash if enabled via config (defaults to false for backwards compatibility)
if (config.get('caching:assets:contentBasedHash:enabled')) {

// Card assets are assembled in memory, so their content hash is always
// available and always matches the bytes we serve — there's no file to miss
// and therefore no reason to gate this on contentBasedHash
const cardType = cardAssets.getCardType(hashPath);
if (cardType !== null) {
hash = cardAssets.getHash(cardType);
} else if (config.get('caching:assets:contentBasedHash:enabled')) {
if (isThemeAsset) {
// For theme assets, use file-based SHA256 hash
hash = getThemeAssetHash(hashPath);
} else if (isPublicAsset) {
// Theme assets resolve relative to the theme's assets/ directory, so an
// explicitly-spelled prefix has to come back off before we look the file up
hash = getThemeAssetHash(hashPath.replace(/^assets\//, ''));
} else {
// For public assets, use file-based SHA256 hash
hash = getPublicAssetHash(hashPath);
}
Expand Down
Loading
Loading