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
5 changes: 5 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changesets

Every change to a published package needs a changeset: `pnpm changeset`,
pick the package and the bump, and write what someone upgrading needs to
know. The text lands verbatim in the package's CHANGELOG.
13 changes: 13 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://unpkg.com/@changesets/config@4.0.1/schema.json",
"baseBranch": "main",
"access": "public",
"ignore": [],
"fixed": [],
"linked": [],
"format": "auto",
"updateInternalDependencies": "patch",
"privatePackages": { "version": false, "tag": false },
"commit": false,
"changelog": "@changesets/cli/changelog"
}
65 changes: 65 additions & 0 deletions .github/workflows/compat-matrix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Runs every plugin suite against every EmDash version we claim to support.
#
# Deliberately NOT named emdash-release.yml: `emdash-plugin release setup`
# generates that file, and this must not collide with it.
#
# Two jobs, because they answer different questions:
#
# declared gating. The versions inside our published range. A failure here
# is our regression and blocks the branch.
# latest informational, never blocks. Runs against whatever EmDash is
# current on npm. EmDash ships a minor every 6-8 days and any minor
# may carry breaking changes, so this is the signal to widen the
# tested-up-to tables in the package READMEs -- or not to.
name: compat matrix

on:
push:
branches: [main]
pull_request:
workflow_dispatch:
schedule:
# Monday 06:00 UTC. EmDash releases land mid-week, so this reports on the
# previous week's minor before the next one arrives.
- cron: "0 6 * * 1"

permissions:
contents: read

jobs:
declared:
name: declared range
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
# The whole point of committing .nvmrc: one file decides the Node
# version here, on a laptop via fnm, and on Cloudflare Workers Builds.
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: ./scripts/compat-matrix.sh 0.39.1 0.40.0

latest:
name: current EmDash (informational)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
- id: resolve
# dist-tags.latest, not the highest semver: emdash@1.0.0 exists on npm
# but was published 2026-04-27 and deprecated, and it outranks every
# real 0.x release in semver order.
run: echo "version=$(npm view emdash dist-tags.latest)" >> "$GITHUB_OUTPUT"
- run: |
echo "current EmDash on npm: ${{ steps.resolve.outputs.version }}"
./scripts/compat-matrix.sh "${{ steps.resolve.outputs.version }}"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.tgz
.DS_Store
.emdash/
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# emdash-plugins

EmDash CMS plugins by Eisbachcode. A pnpm workspace: one published package
per directory under `packages/`, plus `packages/shared`, which is internal,
never published, and inlined into each plugin's build.

`CLAUDE.md` is a symlink to this file.

This file covers what is true across the workspace. Each plugin has its own
`AGENTS.md` and a `skills/creating-plugins/SKILL.md` generated by
`emdash-plugin init`; read both before editing that plugin.

## New packages

Scaffold with `emdash-plugin init <slug>` inside `packages/`, then move
source in. Do not copy one package to start the next. Two things never come
across from a generated package: its nested `pnpm-workspace.yaml`, which
would declare a second workspace root, and its open-ended `"emdash": "*"`
peer (see below).

## Conventions

- Tabs. English in code, comments and docs.
- ESM: internal imports carry `.js`; `import type` for types.
- The `emdash` peer range has a floor and no ceiling, and the floor matches
`release.requires["env:emdash"]` in the manifest. Compatibility with later
EmDash releases is checked by `scripts/compat-matrix.sh`, not promised by
the range.
- Read EmDash APIs from the published release or from `upstream/main`, and
say which. A fork's `main` drifts behind without anyone noticing.
- Anything that talks HTTP takes an injected `fetch`. Whole sync ticks go
through the test host's `host.http.respond()`.
- A sandboxed invocation gets ten subrequests and every `ctx` call is one.
Plugins with a budget test count each invocation's worst case there; run
it after any change that adds a `ctx` call.
- A test must be able to fail on a real regression. Do not assert a config
literal back at itself or restate the implementation.

## Checks

```sh
pnpm install
pnpm typecheck
pnpm test # emdash-plugin validate, then vitest, per package
pnpm build
./scripts/compat-matrix.sh 0.39.1 0.40.0 # suites against other EmDash releases
```

## Releases

Versions come from changesets: add one with `pnpm changeset` for every
change to a published package, written for someone upgrading. The workspace
installs with pnpm 11; `allowBuilds` in `pnpm-workspace.yaml` lets esbuild
and workerd run their install scripts, which the test host needs.
1 change: 1 addition & 0 deletions CLAUDE.md
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,19 @@

Plugins for [EmDash](https://github.com/emdash-cms/emdash) by [Eisbachcode](https://eisbachcode.de).

No plugins are published here yet. The first one, web analytics, moves in once it has run on a live site.
| Package | What it does |
|---|---|
| [`@eisbachcode/emdash-plugin-analytics`](packages/analytics) | Cloudflare Web Analytics on the EmDash dashboard and next to your content: traffic per entry, a setup check, German and English |

## Development

A pnpm workspace. See [AGENTS.md](AGENTS.md) for conventions and checks.

```sh
pnpm install
pnpm test
```

## Licence

MIT, see [LICENSE](LICENSE).
19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@eisbachcode/emdash-plugins",
"private": true,
"type": "module",
"description": "EmDash plugins by Eisbachcode",
"scripts": {
"build": "pnpm run --filter \"./packages/*\" build",
"test": "pnpm run --filter \"./packages/*\" test",
"typecheck": "pnpm run --filter \"./packages/*\" typecheck",
"changeset": "changeset",
"version-packages": "changeset version",
"release": "pnpm build && changeset publish"
},
"packageManager": "pnpm@11.15.1",
"license": "MIT",
"devDependencies": {
"@changesets/cli": "^3.0.3"
}
}
1 change: 1 addition & 0 deletions packages/analytics/.agents/skills
1 change: 1 addition & 0 deletions packages/analytics/.claude/CLAUDE.md
1 change: 1 addition & 0 deletions packages/analytics/.claude/skills
2 changes: 2 additions & 0 deletions packages/analytics/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
dist/
50 changes: 50 additions & 0 deletions packages/analytics/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Agent instructions

Before editing this plugin, read `skills/creating-plugins/SKILL.md` completely. Codex discovers the same directory through `.agents/skills`; Claude discovers it through `.claude/skills` and reads these instructions through `.claude/CLAUDE.md`.
Keep `emdash-plugin.jsonc` aligned with the runtime implementation, declare every capability and host the plugin uses, and run the generated validation, typecheck, test, and build scripts after changes.

## Toolchain

`emdash` is a peer with a floor and no ceiling (`>=0.39.0`, the same as
`env:emdash` in the manifest); the dev dependency stays below the next
major until the toolchain is moved on purpose. Built with
`@emdash-cms/plugin-cli@0.12.0`, `@emdash-cms/plugin-test@0.2.0` and
`@emdash-cms/blocks@0.39.0`; `scripts/compat-matrix.sh` at the repository
root runs the suite against later EmDash releases. A plain `pnpm install`
at the workspace root is enough.

## Two builds

`pnpm run build` runs both:

1. `emdash-plugin build` — the sandbox runtime and the descriptor module. Its
entry is hard-coded to `src/plugin.ts`.
2. `tsdown` — the `./astro` export. It is a separate build on purpose and is
never part of the registry bundle, which stages only `backend.js`,
`manifest.json`, the README and images.

## Things that will bite

- **Never widen a provider query window.** `date_geq` older than
`today − 7` makes Cloudflare serve the whole query from a ~10 % sample,
quantizing every day in it and dropping quiet days entirely. There are
three layers guarding this (`syncWindow` clamps, the adapter refuses,
`decideWrite` will not downgrade). Do not remove one because the other two
exist.
- **Read before write.** Blind upserts cost roughly 5 D1 rows each and blow
through the free plan's daily budget at a 15-minute cadence.
- **Count your bridge calls.** A sandboxed invocation gets ten subrequests
and every `ctx` call spends one, `log` and `cron` included. The sync tick
alternates phases and Refresh only schedules a tick for exactly this
reason. `tests/budget.test.ts` counts each invocation's worst case; run it
after any change that adds a `ctx` call.
- **Block Kit keys are snake_case.** Use the constructors in
`src/ui/blocks.ts`; the renderer silently ignores camelCase.
- `routeCtx.ui` (locale, direction) reaches the widget and the pages only
when the plugin runs sandboxed. Registered in `plugins: []` it is
undefined there, so read `routeCtx.ui?.locale` and fall back to English;
never return empty blocks when it is missing. The editor panel gets it in
both modes, with `ui.entry`.
- **Block Kit keeps no state.** Anything a page needs to remember between
interactions travels in an `action_id`, a button `value` or a table
cursor (see `src/ui/content.ts`).
Loading
Loading