* chore: add PR template, issue templates, and contribution policy Drive-by feature PRs are becoming a problem. This adds guardrails: - PR template with type selection, checklist, and AI disclosure - Bug report issue template (structured YAML form) - Issue config that redirects features to Discussions and disables blank issues - PR compliance workflow that enforces template completion and requires a Discussion link for feature PRs - Contribution policy in CONTRIBUTING.md (acceptance tiers, AI PR rules) - Agent-facing rules in AGENTS.md (follow the template, no bulk changes) * fornat
9.6 KiB
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 enableif 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/cloudflaretemplates/blog-cloudflare->demos/previewtemplates/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.
-
Copy
templates/blog/(ortemplates/blank/) intodemos/:cp -r templates/blog demos/my-site -
Edit
demos/my-site/package.json— set a uniquenamefield. -
Run
pnpm installfrom the root to link workspace dependencies. -
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_collectionsand_emdash_fieldsare 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.mdfor 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
- Create
packages/core/src/database/migrations/NNN_description.ts(zero-padded sequence number). - Export
up(db)anddown(db)functions. - Register it in
packages/core/src/database/migrations/runner.ts— migrations are statically imported, not auto-discovered (Workers bundler compatibility).
Adding an API Route
- Create the file in
packages/core/src/astro/routes/api/. - Start with
export const prerender = false;. - Use
apiError(),handleError(),parseBody()from#api/. - Check authorization with
requirePerm()on all state-changing routes. - 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).
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 typecheckandpnpm --silent lint:jsonpass 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.mdfor architecture and code patterns - Check the documentation site for guides and API reference
- Open an issue or ask in the chat