Skip to content

Latest commit

 

History

History
320 lines (219 loc) · 12.2 KB

File metadata and controls

320 lines (219 loc) · 12.2 KB

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

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.

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 for setup.

Developing Templates

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

# 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:

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:

# 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:

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

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/:

    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:

    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 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:

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):

---
"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):

---
"emdash": minor
---

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

Major (breaking changes) — include migration guidance:

---
"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 for guides and API reference
  • Open an issue or ask in the chat