# Contributing to EmDash

> **Beta.** EmDash is published to npm. During development you work inside the monorepo -- packages use `workspace:*` links, so everything "just works" without publishing.

## Prerequisites

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

## Quick Setup

```bash
git clone <repo-url> && cd emdash
pnpm install
pnpm build          # build all packages (required before first run)
```

### Run the Demo

The `demos/simple/` app is the primary development target. It is kept in sync with `templates/blog/` and uses Node.js + SQLite — no Cloudflare account needed.

```bash
pnpm --filter emdash-demo seed   # seed sample content
pnpm --filter emdash-demo dev    # http://localhost:4321
```

Open the admin at `http://localhost:4321/_emdash/admin`.

In dev mode, passkey auth is bypassed automatically. If you hit the login screen, visit:

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

### Run with Cloudflare (optional)

`demos/cloudflare/` runs on the real `workerd` runtime with D1. See its [README](demos/cloudflare/README.md) for setup.

### Developing Templates

Templates in `templates/` are workspace members and can be run directly:

```bash
# First time: set up database and seed content
pnpm --filter @emdash-cms/template-portfolio bootstrap

# Run the dev server
pnpm --filter @emdash-cms/template-portfolio dev
```

Available templates:

| Template  | Filter Name                      |
| --------- | -------------------------------- |
| Blog      | `@emdash-cms/template-blog`      |
| Portfolio | `@emdash-cms/template-portfolio` |
| Marketing | `@emdash-cms/template-marketing` |

Edit files in `templates/{name}/src/` and changes hot reload.

**Cloudflare variants** (`*-cloudflare`) share source with their base templates via `scripts/sync-cloudflare-templates.sh`. Run that script after editing base template shared files.

Demo/template sync is handled by `scripts/sync-blog-demos.sh`:

- Full sync: `templates/blog` -> `demos/simple`
- Frontend sync (keep runtime-specific config/files):
  - `templates/blog-cloudflare` -> `demos/cloudflare`
  - `templates/blog-cloudflare` -> `demos/preview`
  - `templates/blog` -> `demos/postgres`

To start fresh, delete the database and re-bootstrap:

```bash
rm templates/portfolio/data.db
pnpm --filter @emdash-cms/template-portfolio bootstrap
```

## Development Workflow

### Watch Mode

For iterating on core packages alongside the demo, run two terminals:

```bash
# Terminal 1 — rebuild packages/core on change
pnpm --filter emdash dev

# Terminal 2 — run the demo
pnpm --filter emdash-demo dev
```

Changes to `packages/core/src/` will be picked up by the demo's dev server automatically.

### Checks

Run these before committing:

```bash
pnpm typecheck       # TypeScript (packages)
pnpm typecheck:demos # TypeScript (Astro demos)
pnpm --silent lint:quick   # fast lint (< 1s) — run often
pnpm --silent lint:json    # full type-aware lint (~10s) — run before commits
pnpm format          # auto-format with oxfmt
```

Type checking **must** pass. Lint **must** pass. Don't commit with known failures.

### Tests

```bash
pnpm test                              # all packages
pnpm --filter emdash test            # core only
pnpm --filter emdash test --watch    # watch mode
pnpm test:e2e                          # Playwright (requires demo running)
```

Tests use real in-memory SQLite — no mocking. Each test gets a fresh database.

## Repository Layout

```
emdash/
├── packages/
│   ├── core/              # emdash — the main package (Astro integration + APIs + admin)
│   ├── auth/              # @emdash-cms/auth — passkeys, OAuth, magic links
│   ├── admin/             # @emdash-cms/admin — React admin SPA
│   ├── cloudflare/        # @emdash-cms/cloudflare — CF adapter + plugin sandbox
│   ├── create-emdash/   # create-emdash — project scaffolder
│   ├── gutenberg-to-portable-text/  # WP block → Portable Text converter
│   └── plugins/           # first-party plugins (each dir = package)
├── demos/
│   ├── simple/            # emdash-demo — primary dev/test app (Node.js + SQLite)
│   ├── cloudflare/        # Cloudflare Workers demo (D1)
│   ├── plugins-demo/      # plugin development testbed
│   └── ...
├── templates/             # starter templates (blog, portfolio, marketing + cloudflare variants)
├── docs/                  # public documentation site (Starlight)
└── e2e/                   # Playwright test fixtures
```

The main package is **`packages/core`**. Most of your work will happen there.

## Building Your Own Site (Inside the Monorepo)

The easiest way to build a real site during development is to add it as a workspace member.

1. Copy `templates/blog/` (or `templates/blank/`) into `demos/`:

   ```bash
   cp -r templates/blog demos/my-site
   ```

2. Edit `demos/my-site/package.json` — set a unique `name` field.

3. Run `pnpm install` from the root to link workspace dependencies.

4. Start developing:

   ```bash
   pnpm --filter my-site dev
   ```

Your site will use `workspace:*` links to the local packages, so any changes you make to core will be reflected immediately (with watch mode).

## Key Architectural Concepts

- **Schema lives in the database**, not in code. `_emdash_collections` and `_emdash_fields` are the source of truth.
- **Real SQL tables** per collection (`ec_posts`, `ec_products`), not EAV.
- **Kysely** for all queries. Never interpolate into SQL -- see `AGENTS.md` for the full rules.
- **Handler layer** (`api/handlers/*.ts`) holds business logic. Route files are thin wrappers.
- **Middleware chain**: runtime init -> setup check -> auth -> request context.

## Adding a Migration

1. Create `packages/core/src/database/migrations/NNN_description.ts` (zero-padded sequence number).
2. Export `up(db)` and `down(db)` functions.
3. **Register it** in `packages/core/src/database/migrations/runner.ts` — migrations are statically imported, not auto-discovered (Workers bundler compatibility).

## Adding an API Route

1. Create the file in `packages/core/src/astro/routes/api/`.
2. Start with `export const prerender = false;`.
3. Use `apiError()`, `handleError()`, `parseBody()` from `#api/`.
4. Check authorization with `requirePerm()` on all state-changing routes.
5. Register the route in `packages/core/src/astro/integration/routes.ts`.

## 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.                                                                                                                  |
| **Features**     | Open a [Discussion](https://github.com/emdash-cms/emdash/discussions/categories/ideas) first. Wait for approval before writing code. |
| **Refactors**    | Open a Discussion first. Refactors are opinionated and need alignment.                                                               |
| **Performance**  | Open a Discussion first with benchmarks showing the improvement.                                                                     |

**PRs that add features without a prior approved Discussion will be closed.** This isn't about gatekeeping — it's about not wasting your time on work that might not align with the project's direction. Talk to us first and we'll figure out the right approach together.

### AI-generated PRs

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

- The submitter is responsible for the code's correctness, not the AI tool.
- AI-generated PRs must pass all CI checks, follow the project's code patterns, and include tests.
- The PR template has an AI disclosure checkbox — please check it. This isn't punitive; it helps reviewers know to pay extra attention to edge cases that AI tools commonly miss.
- Bulk/spray PRs across the repo (e.g., "fix all lint warnings", "add types everywhere") will be closed. If you see a pattern worth fixing, open a Discussion first.

### What we don't accept

- **Drive-by feature additions.** If there's no Discussion, there's no PR.
- **Speculative refactors** that don't solve a concrete problem.
- **Dependency upgrades** outside of Renovate/Dependabot. We manage these centrally.
- **"Improvements"** to code you haven't been asked to change (added logging, extra error handling, style changes in unrelated files).

## Changesets

Every PR that changes the behavior of a published package needs a **changeset** — a small Markdown file that describes the change for the CHANGELOG and determines the version bump. Without a changeset, the change won't trigger a package release.

### When you need one

- Bug fixes, features, refactors, or any other change that affects a published package's behavior or API.
- Changes that span multiple packages need one changeset listing all affected packages.
- If a PR makes more than one distinct change, add a separate changeset for each. Each one becomes its own CHANGELOG entry.

### When you don't

- Docs-only changes, test-only changes, CI/tooling changes, or changes to demo apps and templates (these are in the changeset ignore list).

### How to add one

Run from the repo root:

```bash
pnpm changeset
```

This walks you through selecting the affected package(s), the semver bump type, and a description. It creates a randomly-named `.md` file in `.changeset/`.

You can also create one manually — see the existing files in `.changeset/` for the format.

### Writing the description

Start with a present-tense verb describing what the change does, as if completing "This PR...":

- **Adds** — a new feature or capability
- **Fixes** — a bug fix
- **Updates** — an enhancement to existing behavior
- **Removes** — removed functionality
- **Refactors** — internal restructuring with no behavior change

Focus on how the change affects someone **using** the package, not implementation details. The description ends up in the CHANGELOG, which people read once during upgrades.

**Patch** (bug fixes, refactors, small improvements):

```markdown
---
"emdash": patch
---

Fixes CLI `--json` flag so JSON output is clean. Log messages now go to stderr when `--json` is set.
```

**Minor** (new features, non-breaking additions):

```markdown
---
"emdash": minor
---

Adds `scheduled_at` field to content entries, enabling scheduled publishing via the admin UI.
```

**Major** (breaking changes) — include migration guidance:

```markdown
---
"emdash": major
---

Removes the `legacyAuth` option from the integration config. All sites must use passkey authentication.

To migrate, remove `legacyAuth: true` from your `emdash()` config in `astro.config.mjs`.
```

### Which packages?

Only published packages need changesets. Demos, templates, docs, and test fixtures are excluded. The main packages are:

- `emdash` (core)
- `@emdash-cms/admin`, `@emdash-cms/auth`, `@emdash-cms/cloudflare`, `@emdash-cms/blocks`
- `create-emdash`
- First-party plugins (`@emdash-cms/plugin-*`)

When in doubt, run `pnpm changeset` and it will only show packages that aren't ignored.

## Commits and PRs

- Branch from `main`.
- Commit messages: describe _why_, not just _what_.
- Fill out the PR template completely. PRs with an empty template will be closed.
- Ensure `pnpm typecheck` and `pnpm --silent lint:json` pass before pushing.
- Run relevant tests.

## What's Intentionally Missing (For Now)

These are known gaps -- don't try to fix them unless specifically asked:

- **Rate limiting** -- no brute-force protection on auth endpoints
- **Password auth** -- passkeys + magic links + OAuth only, by design
- **Plugin marketplace** -- architecture exists, runtime installation is post-beta
- **Real-time collaboration** -- planned for v1

## Getting Help

- Read `AGENTS.md` for architecture and code patterns
- Check the [documentation site](https://docs.emdashcms.com) for guides and API reference
- Open an issue or ask in the chat
