Skip to content

Latest commit

 

History

History
283 lines (213 loc) · 11.3 KB

File metadata and controls

283 lines (213 loc) · 11.3 KB

AGENTS.md

This file provides guidance to AI Agents when working with code in this repository.

Package Manager

Always use pnpm for all commands. This repository uses pnpm workspaces, not npm.

Monorepo Structure

Ghost is a pnpm + Nx monorepo with three workspace groups:

ghost/* - Core Ghost packages

  • ghost/core - Main Ghost application (Node.js/Express backend)
    • Core server: ghost/core/core/server/
    • Frontend rendering: ghost/core/core/frontend/
  • ghost/admin - Ember.js admin client (legacy, being migrated to React)
  • ghost/i18n - Centralized internationalization for all apps

apps/* - React-based UI applications

Two categories of apps:

Admin Apps (embedded in Ghost Admin):

  • admin-x-settings, admin-x-activitypub - Settings and integrations
  • posts, stats - Post analytics and site-wide analytics
  • Built with Vite + React + @tanstack/react-query

Public Apps (served to site visitors):

  • portal, comments-ui, signup-form, sodo-search, announcement-bar
  • Built as UMD bundles, loaded via CDN in site themes

Foundation Libraries:

  • admin-x-framework - Shared API hooks, routing, utilities
  • admin-x-design-system - Legacy design system (being phased out)
  • shade - New design system (shadcn/ui + Radix UI + react-hook-form + zod)

e2e/ - End-to-end tests

  • Playwright-based E2E tests with Docker container isolation
  • See e2e/CLAUDE.md for detailed testing guidance

Common Commands

Development

corepack enable pnpm           # Enable corepack to use the correct pnpm version
pnpm run setup                 # First-time setup (installs deps + submodules)
pnpm dev                       # Start development (Docker backend + host frontend dev servers)

Building

pnpm build                     # Build all packages (Nx handles dependencies)
pnpm build:clean               # Clean build artifacts and rebuild

Testing

# Unit tests (from root)
pnpm test:unit                 # Run all unit tests in all packages

# Ghost core tests (from ghost/core/)
cd ghost/core
pnpm test:unit                 # Unit tests only
pnpm test:integration          # Integration tests
pnpm test:e2e                  # E2E API tests (not browser)
pnpm test:all                  # All test types

# E2E browser tests (from root)
pnpm test:e2e                  # Run e2e/ Playwright tests

# Running a single test
cd ghost/core
pnpm test:single test/unit/path/to/test.test.js

Linting

pnpm lint                      # Lint all packages
cd ghost/core && pnpm lint     # Lint Ghost core (server, shared, frontend, tests)
cd ghost/admin && pnpm lint    # Lint Ember admin

Database

pnpm knex-migrator migrate     # Run database migrations
pnpm reset:data                # Reset database with test data (1000 members, 100 posts) (requires pnpm dev running)
pnpm reset:data:empty          # Reset database with no data (requires pnpm dev running)

Docker

pnpm docker:build              # Build Docker images
pnpm docker:clean              # Stop containers, remove volumes and local images
pnpm docker:down               # Stop containers

How pnpm dev works

The pnpm dev command uses a hybrid Docker + host development setup:

What runs in Docker:

  • Ghost Core backend (with hot-reload via mounted source)
  • MySQL, Redis, Mailpit
  • Caddy gateway/reverse proxy

What runs on host:

  • Frontend dev servers (Admin, Portal, Comments UI, etc.) in watch mode with HMR
  • Foundation libraries (shade, admin-x-framework, etc.)

Setup:

# Start everything (Docker + frontend dev servers)
pnpm dev

# With optional services (uses Docker Compose file composition)
pnpm dev:analytics             # Include Tinybird analytics
pnpm dev:storage               # Include MinIO S3-compatible object storage
pnpm dev:all                   # Include all optional services

Accessing Services:

  • Ghost: http://localhost:2368 (database: ghost_dev)
  • Mailpit UI: http://localhost:8025 (email testing)
  • MySQL: localhost:3306
  • Redis: localhost:6379
  • Tinybird: http://localhost:7181 (when analytics enabled)
  • MinIO Console: http://localhost:9001 (when storage enabled)
  • MinIO S3 API: http://localhost:9000 (when storage enabled)

Architecture Patterns

Admin Apps Integration (Micro-Frontend)

Build Process:

  1. Admin-x React apps build to apps/*/dist using Vite
  2. ghost/admin/lib/asset-delivery copies them to ghost/core/core/built/admin/assets/*
  3. Ghost admin serves from /ghost/assets/{app-name}/{app-name}.js

Runtime Loading:

  • Ember admin uses AdminXComponent to dynamically import React apps
  • React components wrapped in Suspense with error boundaries
  • Apps receive config via additionalProps() method

Public Apps Integration

  • Built as UMD bundles to apps/*/umd/*.min.js
  • Loaded via <script> tags in theme templates (injected by {{ghost_head}})
  • Configuration passed via data attributes

i18n Architecture

Centralized Translations:

  • Single source: ghost/i18n/locales/{locale}/{namespace}.json
  • Namespaces: ghost, portal, signup-form, comments, search
  • 60+ supported locales
  • Context descriptions: ghost/i18n/locales/context.json — every key must have a non-empty description

Translation Workflow:

pnpm --filter @tryghost/i18n translate          # Extract keys from source, update all locale files + context.json
pnpm --filter @tryghost/i18n lint:translations   # Validate interpolation variables across locales

translate is run as part of pnpm --filter @tryghost/i18n test. In CI, it fails if translation keys or context.json are out of date (failOnUpdate: process.env.CI). Always run pnpm --filter @tryghost/i18n translate after adding or changing t() calls.

Rules for Translation Keys:

  1. Never split sentences across multiple t() calls. Translators cannot reorder words across separate keys. Instead, use @doist/react-interpolate to embed React elements (links, bold, etc.) within a single translatable string.
  2. Always provide context descriptions. When adding a new key, add a description in context.json explaining where the string appears and what it does. CI will reject empty descriptions.
  3. Use interpolation for dynamic values. Ghost uses {variable} syntax: t('Welcome back, {name}!', {name: firstname})
  4. Use <tag> syntax for inline elements. Combined with @doist/react-interpolate: t('Click <a>here</a> to retry') with mapping={{ a: <a href="..." /> }}

Correct pattern (using Interpolate):

import Interpolate from '@doist/react-interpolate';

<Interpolate
    mapping={{ a: <a href={link} /> }}
    string={t('Could not sign in. <a>Click here to retry</a>')}
/>

Incorrect pattern (split sentences):

// BAD: translators cannot reorder "Click here to retry" relative to the first sentence
{t('Could not sign in.')} <a href={link}>{t('Click here to retry')}</a>

See apps/portal/src/components/pages/email-receiving-faq.js for a canonical example of correct Interpolate usage.

Build Dependencies (Nx)

Critical build order (Nx handles automatically):

  1. shade + admin-x-design-system build
  2. admin-x-framework builds (depends on #1)
  3. Admin apps build (depend on #2)
  4. ghost/admin builds (depends on #3, copies via asset-delivery)
  5. ghost/core serves admin build

CSS Architecture

TailwindCSS v4 Setup

Ghost Admin uses TailwindCSS v4 via the @tailwindcss/vite plugin. CSS processing is centralized — only apps/admin/vite.config.ts loads the @tailwindcss/vite plugin. All embedded React apps (posts, stats, activitypub, admin-x-settings, admin-x-design-system) are scanned from this single entry point.

Entry Point

apps/admin/src/index.css is the main CSS entry point. It contains:

  • @source directives that scan class usage in shade, posts, stats, activitypub, admin-x-settings, admin-x-design-system, and kg-unsplash-selector
  • @import "@tryghost/shade/styles.css" which loads the Shade design system styles

Shade Styles

apps/shade/styles.css uses unlayered Tailwind imports:

@import "tailwindcss/theme.css";
@import "./preflight.css";
@import "tailwindcss/utilities.css";
@import "tw-animate-css";
@import "./tailwind.theme.css";

Why unlayered: Ember's legacy CSS (.flex, .hidden, etc.) is unlayered. If Tailwind utilities were in a @layer, they would lose to Ember's unlayered CSS in the cascade. Keeping both unlayered means source order determines specificity.

Theme tokens/variants/animations are defined in CSS (apps/shade/tailwind.theme.css + runtime vars in styles.css), so there is no JS @config bridge in the Admin runtime lane. tw-animate-css is the v4 replacement for tailwindcss-animate.

Critical Rule: Embedded Apps Must NOT Import Shade Independently

Apps consumed via @source (posts, stats, activitypub) must NOT import @tryghost/shade/styles.css in their own CSS. Doing so causes duplicate Tailwind utilities and cascade conflicts. All Tailwind CSS is generated once via the admin entry point.

Public Apps

Public-facing apps (comments-ui, signup-form, sodo-search, portal, announcement-bar) remain on TailwindCSS v3. They are built as UMD bundles for CDN distribution and are independent of the admin CSS pipeline.

Legacy Apps

admin-x-design-system and admin-x-settings are consumed via @source in admin's centralized v4 pipeline for production, and both packages build with CSS-first Tailwind v4 setup.

Code Guidelines

Commit Messages

When the user asks you to create a commit or draft a commit message, load and follow the commit skill from .agents/skills/commit.

When Working on Admin UI

  • New features: Build in React (apps/admin-x-* or apps/posts)
  • Use: admin-x-framework for API hooks (useBrowse, useEdit, etc.)
  • Use: shade design system for new components (not admin-x-design-system)
  • Translations: Add to ghost/i18n/locales/en/ghost.json

When Working on Public UI

  • Edit: apps/portal, apps/comments-ui, etc.
  • Translations: Separate namespaces (portal.json, comments.json)
  • Build: UMD bundles for CDN distribution

When Working on Backend

  • Core logic: ghost/core/core/server/
  • Database Schema: ghost/core/core/server/data/schema/
  • API routes: ghost/core/core/server/api/
  • Services: ghost/core/core/server/services/
  • Models: ghost/core/core/server/models/
  • Frontend & theme rendering: ghost/core/core/frontend/

Design System Usage

  • New components: Use shade (shadcn/ui-inspired)
  • Legacy: admin-x-design-system (being phased out, avoid for new work)

Analytics (Tinybird)

  • Local development: pnpm dev:analytics (starts Tinybird + MySQL)
  • Config: Add Tinybird config to ghost/core/config.development.json
  • Scripts: ghost/core/core/server/data/tinybird/scripts/
  • Datafiles: ghost/core/core/server/data/tinybird/

Troubleshooting

Build Issues

pnpm fix                       # Clean cache + node_modules + reinstall
pnpm build:clean               # Clean build artifacts
pnpm nx reset                  # Reset Nx cache

Test Issues

  • E2E failures: Check e2e/CLAUDE.md for debugging tips
  • Docker issues: pnpm docker:clean && pnpm docker:build