Toast
Contributor

Contribution Workflow

Git hooks, CI pipeline, commit conventions, and pull request process.

This page covers the mechanics of contributing to Toast — from making your first commit to getting a PR merged.

The Golden Rule

Run pnpm check before pushing. It auto-fixes lint and formatting issues, then runs typecheck, tests, and build. If it passes, your code is ready.

pnpm check       # Auto-fix, then verify everything
pnpm check:strict # Verify only, no auto-fix (what CI runs)

Git Hooks

Toast uses Husky to run automated checks at key git lifecycle points. Never bypass hooks with --no-verify — fix the underlying issue instead.

commit-msg

Validates your commit message format using commitlint:

pnpm commitlint --edit "$1"

Rejects commits that don't follow the conventional commit format (see Commit Format below).

pre-commit

Runs lint-staged to auto-fix staged files:

  • TypeScript/JavaScript — Biome lint fix + Prettier format
  • JSON/CSS/YAML/Markdown — Prettier format
  • SQL migrations — Syncs docs/patterns/database.md migration log

Changes made by lint-staged are automatically re-staged, so your commit includes the fixes.

pre-push

The final quality gate before code leaves your machine:

pnpm check:strict

This runs lint, format check, typecheck, tests with coverage enforcement, and build — all in check-only mode (no auto-fix). If the pre-commit hook did its job, this should pass cleanly.

Commit Format

Toast enforces Conventional Commits:

<type>(optional-scope): short summary

Allowed Types

TypeUse When
featAdding a new feature
fixFixing a bug
refactorRestructuring code without changing behavior
testAdding or updating tests
docsDocumentation changes
choreMaintenance tasks (dependencies, configs)
buildBuild system or external dependency changes
ciCI/CD pipeline changes
perfPerformance improvements
styleCode style changes (formatting, semicolons)
revertReverting a previous commit

Examples

feat(api): add content filter by author
fix(admin): handle empty upload response
docs: update Railway deployment troubleshooting
refactor(db): extract shared query helpers
test(api): add coverage for audit log pagination

Commit Body

The body should explain why the change was made. The diff shows what changed; the message explains why it matters:

fix(api): return 404 for unpublished content in public API

The public content endpoint was returning draft posts to unauthenticated
users. This scopes the query to published status only, matching the
behavior described in the Content API docs.

Closes #456

CI Pipeline

Every push and PR triggers the CI workflow (.github/workflows/ci.yml) with seven jobs:

Job Dependency Graph

pr-title ─────────────┐
commit-messages ───────┤
quality ───────────────┼─→ ci-gate ─→ docker-build
coverage ──────────────┤
integration-tests ─────┘

Job Details

JobWhat It DoesTimeout
pr-titleValidates PR title follows conventional commit format5 min
commit-messagesValidates all commit messages in the PR5 min
qualityTypecheck, lint, format check, build10 min
coverageRuns tests with coverage enforcement10 min
integration-testsRuns integration tests against a real PostgreSQL10 min
ci-gateMeta-job: fails if any required job failed
docker-buildBuilds and pushes Docker images to GHCR15 min

The ci-gate job is the branch protection check — it aggregates all required job results into a single pass/fail.

docker-build runs after ci-gate passes and builds three images: toast-api, toast-admin, and toast-docs. Images are tagged with both the branch/PR identifier and the commit SHA.

Pull Request Process

Creating a PR

  1. Create a feature branch from main
  2. Make your changes, ensuring pnpm check passes
  3. Push and open a PR — the title must follow conventional commit format
  4. Wait for CI to pass
  5. Address reviewer feedback

PR Title Format

PR titles follow the same format as commit messages:

feat(api): add content filter by author

This is validated by the pr-title CI job using action-semantic-pull-request.

Code Review Expectations

When a reviewer flags a concern:

  • Test your assumptions — don't just reason about it, verify it works
  • Be careful with patterns — shell globs, regex, and pathspecs behave differently across tools
  • "It should work" ≠ "I verified it works" — run the actual command before dismissing feedback

When dismissing a suggestion, explain your reasoning and confirm you tested it.

Requesting Copilot Re-review

After addressing Copilot review comments:

gh api -X POST repos/TryGhost/Toast/pulls/{PR_NUMBER}/requested_reviewers \
  -f 'reviewers[]=copilot-pull-request-reviewer[bot]'

gh pr edit --add-reviewer Copilot does not reliably trigger a re-review. The API call above is the reliable method.

Test Coverage

New code requires 100% test coverage. The pre-push hook and CI enforce this. See Testing Patterns for:

  • Unit test conventions and mocking
  • Integration test setup with real PostgreSQL
  • Snapshot testing patterns

Preserving Learnings

Discoveries made while working on issues should be captured permanently:

Size of LearningWhere to Capture It
SmallIssue comments or commit message
MediumUpdate relevant docs or code comments
LargeWrite an ADR in docs/decisions/

Examples worth capturing: why a library was chosen, non-obvious configuration, gotchas, helpful documentation links, and decisions that were considered but rejected.

Infrastructure Changes

When adding environment variables or changing deployment infrastructure:

ChangeFiles to Update
New environment variable.env.example, docs/railway-setup.md, docs/patterns/docker.md
New build-time variableAbove + relevant Dockerfile (add ARG/ENV)
New Railway servicedocs/railway-setup.md
Docker build changesdocs/patterns/docker.md, relevant Dockerfile
GitHub Actions secretsdocs/railway-setup.md (section 16)

On this page