Files
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

264 lines
12 KiB
Markdown

# 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](AGENTS.md).
## Prerequisites
- **Node.js** 22+
- **pnpm** 10+ (`corepack enable` if you don't have it)
- **Git**
## Setup
```bash
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.
```bash
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](demos/cloudflare/README.md).
### Templates
Templates in `templates/` are workspace members and runnable directly:
```bash
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:
```bash
# 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:
```bash
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.
### Building your own site in the monorepo
Copy a template into `demos/`, give it a unique `name` in `package.json`, install, and run:
```bash
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](https://docs.emdashcms.com/contributing/translating/). |
| **Features** | Open a [Discussion](https://github.com/emdash-cms/emdash/discussions/categories/ideas) 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
Every PR that changes a published package's behavior needs a **changeset** -- a small Markdown file that describes the change for the CHANGELOG and determines the version bump. Without one, the change won't trigger a release.
### When you need one
- Bug fixes, features, refactors, or anything that affects a published package's behavior or API.
- Multi-package changes need one changeset listing all affected packages.
- A PR making multiple distinct changes can include a changeset per change -- each becomes its own CHANGELOG entry.
### When you don't
- Docs-only, test-only, CI/tooling changes, or changes to demos and templates (these are in the ignore list -- see `.changeset/config.json`).
### How
```bash
pnpm changeset
```
The CLI walks you through affected packages, bump type, and description. Edit the resulting `.md` file in `.changeset/` if needed.
### Writing the description
A changeset is the **release note a user reads while upgrading** -- it lands verbatim in the CHANGELOG. It is not a commit message, a PR description, or a summary of your diff. Don't paste your PR text into it: those explain the change to a reviewer reading the code, the changeset explains the effect to someone who will run the new version.
Write for that reader:
- Start with a present-tense verb -- **Fixes** (bug), **Adds** (feature), **Updates** (enhancement), **Removes** (removed functionality), **Refactors** (no behavior change).
- Describe the observable effect -- what's different for someone using the package.
- Leave out internal mechanics -- file names, function names, which catalog entry you bumped, how you implemented it. If a sentence only makes sense to someone who has read the diff, it doesn't belong here.
- For a breaking change, include the migration step.
One sentence is often enough.
```diff
- # too low-level -- reads like a commit message
- Align the catalog so identity-resolver's lexicons peer resolves; migrates parseCanonicalResourceUri off the result-object API in backfill.ts.
+ # right altitude -- the effect on the user
+ Fixes peer dependency warnings on install caused by mismatched `@atcute` package versions.
```
**Patch** (bug fix or small improvement):
```markdown
---
"emdash": patch
---
Fixes CLI `--json` flag so JSON output is clean. Log messages now go to stderr when `--json` is set.
```
**Minor** (new non-breaking feature):
```markdown
---
"emdash": minor
---
Adds `scheduled_at` field to content entries, enabling scheduled publishing via the admin UI.
```
**Major** (breaking change) -- 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`.
```
## Internationalization
The admin UI is translatable using [Lingui](https://lingui.dev). All user-visible strings in `packages/admin/src/` should be wrapped.
```tsx
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](https://docs.emdashcms.com/contributing/translating/) for the full contributor guide.
For RTL rules and the full Lingui pattern reference, see [AGENTS.md § Admin UI: Localization](AGENTS.md#admin-ui-localization-lingui).
## Getting Help
- [AGENTS.md](AGENTS.md) -- architecture and code patterns
- [TRIAGE.md](TRIAGE.md) -- guidance for community triagers
- [docs.emdashcms.com](https://docs.emdashcms.com) -- user guides and API reference
- [Discussions](https://github.com/emdash-cms/emdash/discussions) -- ask questions, propose features
- [Issues](https://github.com/emdash-cms/emdash/issues) -- bug reports