Skip to content

Latest commit

 

History

History
211 lines (147 loc) · 10.1 KB

File metadata and controls

211 lines (147 loc) · 10.1 KB

Contributing to EmDash

EmDash is published to npm and in active use. During development you work inside the monorepo -- packages use workspace:* links, so everything works without publishing.

This guide covers setup, policy, and the rules around opening a PR. For code patterns (SQL, API routes, authorization, performance, Lingui, RTL, etc.), see AGENTS.md.

Prerequisites

  • Node.js 22+
  • pnpm 10+ (corepack enable if you don't have it)
  • Git

Setup

git clone https://github.com/emdash-cms/emdash.git && cd emdash
pnpm install
pnpm build   # required before first run

Run the demo

demos/simple/ is the primary development target. Node.js + SQLite, no Cloudflare account needed.

cd demos/simple
pnpm dev    # http://localhost:4321

Open the admin at http://localhost:4321/_emdash/admin. The setup wizard runs on first launch.

In dev, skip passkey auth with the dev bypass:

http://localhost:4321/_emdash/api/setup/dev-bypass?redirect=/_emdash/admin

Demo sites apply their seed/seed.json automatically on the first request when the database is empty -- there's no separate seed command.

demos/cloudflare/ runs on the real workerd runtime with D1. See its README.

Templates

Templates in templates/ are workspace members and runnable directly:

cd templates/portfolio
pnpm dev

Available templates: blank, starter, blog, portfolio, marketing, plus a -cloudflare variant of each runnable template. Seed content is applied automatically on first request. To start fresh, delete the local database (data.db or the D1 binding) and restart the dev server.

Watch mode

When iterating on packages/core alongside a demo, run two terminals:

# Terminal 1
cd packages/core && pnpm dev

# Terminal 2
cd demos/simple && pnpm dev

Core changes propagate to the demo automatically.

Repository Layout

Directory What it is
packages/core/ Main emdash package -- Astro integration, REST API, database, schema, plugins
packages/admin/ React admin UI SPA (@emdash-cms/admin)
packages/auth/ Auth -- passkeys, OAuth, magic links (@emdash-cms/auth)
packages/cloudflare/ Cloudflare Workers adapter + plugin sandbox
packages/blocks/ Portable Text block definitions
packages/create-emdash/ create-emdash CLI scaffolder
packages/plugins/ First-party plugins
demos/ Dev/test apps (simple, cloudflare, postgres, ...)
templates/ Starter templates
docs/ Documentation site (Starlight)
e2e/ Playwright test infrastructure
i18n/ Translation status dashboard (Lunaria)

Checks

Run before pushing:

pnpm typecheck   # TypeScript (packages)
pnpm lint        # full type-aware lint
pnpm format      # auto-format with oxfmt (tabs)
pnpm test        # all packages
pnpm test:e2e    # Playwright

pnpm build is required before the first typecheck in a fresh checkout. Scoped package typechecks such as pnpm --filter @emdash-cms/plugin-cli typecheck resolve internal workspace type declarations from dist/, which the build emits; this matches CI's build-then-typecheck order.

Tests use real in-memory SQLite -- no mocking. Each test gets a fresh database. Typecheck and lint must pass.

Visual regression tests

The admin UI has a Playwright visual-regression suite (e2e/tests/visual-regression.spec.ts) that screenshots key screens in both LTR (English) and RTL (Arabic). It is gated behind EMDASH_VISUAL=1 so it stays out of the default pnpm test:e2e run:

EMDASH_VISUAL=1 pnpm test:e2e visual-regression

Baselines are platform-specific. CI (Linux) is the source of truth -- committed baselines are *-chromium-linux.png. Locally generated macOS/Windows baselines (*-darwin.png, *-win32.png) are gitignored; never commit them, they won't match CI.

When a PR changes how a screen renders, the Visual Regression check goes red and a bot posts a sticky comment with the diff images. A maintainer reviews the diffs and, if the change is intended, comments /accept-baselines. The Visual Regression — Apply job then commits the regenerated Linux baselines to the PR branch. Baselines are never updated automatically on push -- a maintainer must accept each change.

Building your own site in the monorepo

Copy a template into demos/, give it a unique name in package.json, install, and run:

cp -r templates/blog demos/my-site
# edit demos/my-site/package.json to set a unique name
pnpm install
cd demos/my-site && pnpm dev

Your site uses workspace:* links, so core changes are reflected immediately.

Contribution Policy

What we accept

Type Process
Bug fixes Open a PR directly. Include a failing test that reproduces the bug.
Docs / typos Open a PR directly.
Translations Open a PR directly. See Translating EmDash.
Features Open a Discussion and wait for maintainer approval.
Refactors Open a Discussion first.
Performance Open a Discussion first with benchmarks.

Feature PRs without prior maintainer approval will be closed. Not gatekeeping -- it's about not wasting your time on work that might not align with the project's direction.

What we don't accept

  • Drive-by feature additions. No Discussion, no PR.
  • Speculative refactors that don't solve a concrete problem.
  • Dependency upgrades outside Renovate/Dependabot.
  • Drive-by "improvements" in code unrelated to your change.
  • Bulk/spray PRs ("fix all lint warnings", "add types everywhere"). Open a Discussion first.

AI-generated PRs

AI-assisted contributions are welcome and held to the same quality bar as any other PR:

  • The submitter is responsible for correctness, not the tool.
  • AI-generated PRs must pass CI, follow project patterns, and include tests.
  • Check the PR template's AI disclosure box and name the model/tool (e.g. Claude Opus 4.7, GPT-5.5, Cursor + Sonnet 4.6). This isn't punitive -- it helps reviewers focus on edge cases that AI tools tend to miss and run the review pass with a different model family.

PR rules

  • Branch from main.
  • Fill out the PR template completely. PRs with an empty or missing template will be closed automatically. The template is loaded by the GitHub UI; if you create a PR via API/CLI, copy .github/PULL_REQUEST_TEMPLATE.md into the body.
  • pnpm typecheck and pnpm lint must pass before pushing.
  • Run relevant tests.
  • Commit messages describe why, not just what.

Changesets

Follow Writing and reviewing changesets for when a change needs one, package bump types, the user-facing writing standard, examples, and review criteria.

Create the file with the Changesets CLI, then edit the generated Markdown:

pnpm changeset

Internationalization

The admin UI is translatable using Lingui. All user-visible strings in packages/admin/src/ should be wrapped.

import { Trans, useLingui } from "@lingui/react/macro";

function MyComponent() {
	const { t } = useLingui();
	return (
		<div>
			<h1>{t`Settings`}</h1>
			<p>{t`Authentication error: ${error}`}</p>
			<p>
				<Trans>
					Don't have an account? <a href="/signup">Sign up</a>
				</Trans>
			</p>
		</div>
	);
}

Wrap button labels, headings, descriptions, error messages, placeholders, and aria-label on interactive controls. Don't wrap log messages, developer-facing errors, brand names, or URLs. For decorative elements, prefer aria-hidden="true" over a translated aria-label.

Don't include messages.po changes in feature or bugfix PRs. A workflow runs pnpm locale:extract on merge to main and commits catalog updates automatically. Including extracted PO changes in non-translation PRs creates churn and merge conflicts because line-number references shift on every edit. If you ran extraction locally and ended up with .po changes, revert them before opening the PR.

Translation PRs are the exception -- see Translating EmDash for the full contributor guide.

For RTL rules and the full Lingui pattern reference, see AGENTS.md § Admin UI: Localization.

Getting Help