Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/deploy-blog-cloudflare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Deploy blog-cloudflare

on:
push:
branches: [main]
paths:
- "blog-cloudflare/**"

workflow_dispatch:

jobs:
deploy:
name: Deploy to Cloudflare Workers
runs-on: ubuntu-latest
defaults:
run:
working-directory: blog-cloudflare

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install
run: pnpm install --no-frozen-lockfile

- name: Deploy
run: pnpm deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
116 changes: 115 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1 +1,115 @@
This repo is auto-synced from [emdash-cms/emdash](https://github.com/emdash-cms/emdash). Do not open PRs here — make changes in the `templates/` directory of the main repo instead.
> **This repo is auto-synced from [emdash-cms/emdash](https://github.com/emdash-cms/emdash).** Do not open PRs here — make changes in the `templates/` directory of the main repo instead. External PRs are closed automatically.

# EmDash Templates

A collection of starter templates for [EmDash](https://github.com/emdash-cms/emdash), a full-stack TypeScript CMS built on Astro. Each template is a self-contained Astro site with EmDash wired up, seed data for demo content, and its own `CLAUDE.md`.

## Repository Layout

```
templates/
├── blank/ # Minimal: one page, EmDash wired, nothing else
├── blog/ # Blog (Node.js variant: SQLite + local storage)
├── blog-cloudflare/ # Blog (Cloudflare variant: D1 + R2)
├── marketing/ # Landing page with content blocks (Node.js)
├── marketing-cloudflare/ # Landing page (Cloudflare)
├── portfolio/ # Portfolio/case studies (Node.js)
├── portfolio-cloudflare/ # Portfolio (Cloudflare)
├── starter/ # General-purpose base (Node.js)
├── starter-cloudflare/ # General-purpose base (Cloudflare)
└── screenshots.json # Page paths used by screenshot tooling
```

Each template directory is standalone — it has its own `package.json`, `astro.config.mjs`, `seed/seed.json`, and source tree. There is no monorepo workspace at the repo root; every template is installed and built independently.

## Template Variants

Each template (except `blank`) has two variants:

| Variant | Adapter | Database | Storage |
|---|---|---|---|
| **Node.js** (`blog`, `marketing`, etc.) | `@astrojs/node` | SQLite via `better-sqlite3` | Local filesystem |
| **Cloudflare** (`blog-cloudflare`, etc.) | `@astrojs/cloudflare` | Cloudflare D1 | Cloudflare R2 |

Cloudflare variants include a `wrangler.jsonc` and a `src/worker.ts` entry point. Node.js variants do not.

## Working in a Template

All commands run from inside the template directory, not the repo root.

```bash
cd blog # or any other template

pnpm install # install deps (pnpm is the package manager)
npx emdash dev # start dev server: runs migrations, seeds, generates types
npx emdash types # regenerate TypeScript types from schema (after schema changes)
pnpm typecheck # astro check (TypeScript)
pnpm build # production build
pnpm preview # preview production build (Node.js variants only)
```

- Site: `http://localhost:4321`
- CMS admin UI: `http://localhost:4321/_emdash/admin`

## Key Files in Every Template

| File | Purpose |
|---|---|
| `astro.config.mjs` | Astro config: `emdash()` integration, adapter, database, storage, plugins |
| `seed/seed.json` | Schema + demo content: collections, fields, taxonomies, menus, widgets |
| `emdash-env.d.ts` | Auto-generated TypeScript types for collections (regenerated on `emdash dev`) |
| `src/live.config.ts` | EmDash loader registration — boilerplate, do not modify |
| `src/layouts/Base.astro` | Base layout: menus, search, EmDash head/body hooks |
| `src/pages/` | All Astro pages (server-rendered) |
| `src/styles/theme.css` | CSS custom properties and global styles |

## EmDash Conventions (apply across all templates)

- **Always server-rendered.** All content pages use `output: "server"` in `astro.config.mjs`. Never use `getStaticPaths()` for CMS content — slugs are dynamic.
- **Image fields are objects.** A field typed `image` returns `{ src, alt, meta, ... }`, not a plain string. Always render with `<Image image={...} />` from `"emdash/ui"`.
- **Two IDs per entry.** `entry.id` is the URL slug; `entry.data.id` is the database ULID. API calls like `getEntryTerms` and `Comments` take `entry.data.id`. URLs use `entry.id`.
- **Cache hint.** Always call `Astro.cache.set(cacheHint)` on pages that query content. The `cacheHint` is returned by `getEmDashEntry` and `getEmDashCollection`.
- **Taxonomy names.** In queries, use the exact `"name"` field from the seed (e.g. `"tag"`, `"category"`), not the plural label.
- **Parallel queries.** Independent data fetches (e.g. tags + related posts) should run with `Promise.all` to avoid serial round-trips.
- **Slug decoding.** Use `decodeSlug(Astro.params.slug)` before passing to `getEmDashEntry` — handles encoded characters in URL params.

## Template-Specific Notes

### blog / blog-cloudflare
Collections: `posts`, `pages`. Features: reading time, tag/category taxonomy, full-text search, RSS, comments, sidebar widgets, TOC, bylines, SEO meta. Custom utility at `src/utils/reading-time.ts`.

### marketing / marketing-cloudflare
Collections: `pages` with a Portable Text `content` field. Custom inline plugin at `src/plugins/marketing-blocks/` registers five block types (`marketing.hero`, `marketing.features`, `marketing.testimonials`, `marketing.pricing`, `marketing.faq`). Rendered by `src/components/MarketingBlocks.astro`. Also uses `@emdash-cms/plugin-forms` for the contact form.

### portfolio / portfolio-cloudflare
Collections: `projects`. Features: tag filtering, RSS, contact form, case-study pages.

### starter / starter-cloudflare
Minimal opinionated starting point. Collections: `posts`, `pages`. Tag and category taxonomy. No custom design — intended as a base to build from.

### blank
Single `src/pages/index.astro`. No collections, no seed content, no styling. The absolute minimum to start from scratch.

## CI

GitHub Actions (`.github/workflows/ci.yml`) runs on every push and PR to `main`:

1. **Typecheck** — `pnpm typecheck` in each template directory
2. **Build** — `pnpm build` in each template directory
3. **Smoke test** — starts the dev server for non-Cloudflare templates, hits `/_emdash/api/setup/dev-bypass` to create a dev session, then verifies `/_emdash/admin` renders without errors

All nine templates are tested in parallel via a matrix strategy. Cloudflare variants skip the smoke test (no local D1/R2 in CI).

## Skills for Working on Templates

When working inside a specific template, load the relevant skill:

- **building-emdash-site** — Content queries, Portable Text rendering, schema design, seed files, menus, widgets, SEO, comments, bylines
- **emdash-cli** — CLI commands for content management, seeding, type generation
- **creating-plugins** — Building EmDash plugins (hooks, storage, admin UI, API routes, Portable Text block types)

Skills are also available as files in each template's `.agents/skills/` directory.

## EmDash Documentation

The live docs are available as an MCP server at `https://docs.emdashcms.com/mcp`. Each template ships with `.mcp.json`, `.cursor/mcp.json`, and `.vscode/mcp.json` so Claude Code, Cursor, and VS Code auto-discover it. When verifying an API, hook, config option, or field type, call `search_docs` against the live docs rather than relying on training-data recall.
1 change: 1 addition & 0 deletions blog-cloudflare/wrangler.jsonc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "crosbynews",
"account_id": "bfadb24d6e09a43ae3187aa145992b68",
"main": "./src/worker.ts",
"compatibility_date": "2026-02-24",
"compatibility_flags": ["nodejs_compat"],
Expand Down
Loading