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
File renamed without changes.
5 changes: 5 additions & 0 deletions .changeset/analytics-own-repository.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@eisbachcode/emdash-plugin-analytics": patch
---

The plugin now has a repository of its own: https://github.com/eisbachcode/emdash-plugin-analytics. The old address, `eisbachcode/emdash-plugins`, redirects there. The package's repository, homepage and issue links point to the new address; the plugin itself is unchanged.
File renamed without changes.
File renamed without changes.
12 changes: 5 additions & 7 deletions .github/workflows/compat-matrix.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Runs every plugin suite against every EmDash version we claim to support.
# Runs the plugin's 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.
#
# Three jobs, because they answer different questions:
#
# tests gating. Typecheck, manifest validation and every suite on the
# EmDash version each package's harness pins. This is the
# tests gating. Typecheck, manifest validation and the suite on the
# EmDash version the harness pins. This is the
# baseline that tells a harness breakage from a plugin regression.
# declared gating, one job per version so they run in parallel. 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.
# tested-up-to line in the README -- or not to.
name: compat matrix

on:
Expand Down Expand Up @@ -49,9 +49,7 @@ jobs:
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
# One package at a time, like the compat script: suites running in
# parallel on one runner are untested.
- run: pnpm --filter "./packages/*" --workspace-concurrency=1 run test
- run: pnpm test

declared:
name: EmDash ${{ matrix.emdash }}
Expand Down
120 changes: 78 additions & 42 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,80 @@
# 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).
# emdash-plugin-analytics

The EmDash analytics plugin by Eisbachcode: `@eisbachcode/emdash-plugin-analytics` on npm,
`@eisbachcode.de/analytics` in the EmDash registry.

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` runs the suite
against later EmDash releases. A plain `pnpm install` 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 in
both install modes from EmDash 1.0.1; before that, a `plugins: []`
install gets it undefined there. 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 on every version, with `ui.entry`.
- **MCP schemas never reach the runtime.** `emdash-plugin build` strips
the `mcp` property of `src/plugin.ts` from the bundle and writes the zod
schemas into the manifest as JSON Schema. `src/tools/declare.ts` is
referenced only from there, so zod stays a dev dependency and out of the
bundle; a schema imported by a route handler would pull it back in. The
handlers in `src/tools/load.ts` validate by hand, because the routes are
also reachable over HTTP without the MCP server's validation.
- **An MCP output schema is strict.** Every object becomes
`additionalProperties: false` and the MCP server rejects an answer that
does not match, so a loader's result and its declared output have to
agree key for key. `tests/tools.test.ts` checks each answer against the
schema the build wrote.
- **In `plugins: []`, MCP tools need EmDash 0.42.0 or later.** Older
in-process loaders read tools from the runtime module, where the build
removes them, so a `plugins: []` install on 0.39 to 0.41 lists none.
Sandboxed installs have them on every supported version.
- **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`).

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

Expand All @@ -41,30 +83,23 @@ peer (see below).
```sh
pnpm install
pnpm typecheck
pnpm test # emdash-plugin validate, then vitest, per package
pnpm test # emdash-plugin validate, then vitest
pnpm build
./scripts/compat-matrix.sh 0.39.0 0.39.1 0.40.0 0.40.1 0.41.0 0.42.0 1.0.1:0.2.6 # suites against other EmDash releases
./scripts/compat-matrix.sh 0.39.0 0.39.1 0.40.0 0.40.1 0.41.0 0.42.0 1.0.1:0.2.6 # the suite 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.

A package that has not been released yet stays `"private": true`. The release
workflow publishes every public package whose version is missing from npm,
changeset or not, so a public package would go out with its first merge to
`main`. The release PR removes the flag and adds the package's first
changeset. That first publish is manual (`pnpm publish` in the package):
npm's trusted-publisher setting can only be added to a package that exists.
change that ships, written for someone upgrading.

Listing images go in `images/` and are declared under
`release.artifacts.screenshots` in the manifest. Never use a `screenshots/`
folder: the registry bundle takes it whole and refuses a bundle over 256 KB
or an image over 128 KB. The registry also refuses a manifest `description`
over 140 graphemes, which `emdash-plugin validate` does not check.

Never name a package script `publish`, `version` or `prepare`: npm and pnpm
Never name a script `publish`, `version` or `prepare`: npm and pnpm
run scripts with those names on their own during a publish or a version
bump. `emdash-plugin init` generates `"publish": "emdash-plugin publish"`,
which would push to the EmDash registry after every `npm publish`; here it
Expand All @@ -73,14 +108,15 @@ is `registry:publish`, and `prepublishOnly` builds before any publish.
`.github/workflows/release.yml` does the npm side: with changesets on main
it opens a "Version Packages" pull request, and merging that publishes
through npm trusted publishing. npm trusts that workflow by its file name,
so renaming it breaks publishing until the setting on npmjs.com changes too.
so renaming it, or the repository, breaks publishing until the setting on
npmjs.com changes too. The registry profile names the repository as well
(`profile setup --repository`).
After an npm publish it calls `emdash-release.yml`, generated by
`emdash-plugin release setup` and edited since, which publishes the same
versions of every package with an `emdash-plugin.jsonc` to the EmDash
registry. Its build and publish run in separate jobs; only the publish job
version to the EmDash registry. Its build and publish run in separate jobs; only the publish job
holds `id-token`, and it installs and builds nothing. The npm publish job
runs in the `npm` environment, which the npm trusted publisher requires.
Keep both.

The workspace installs with pnpm 11; `allowBuilds` in `pnpm-workspace.yaml` lets esbuild
and workerd run their install scripts, which the test host needs.
The repository installs with pnpm 11; `allowBuilds` in `pnpm-workspace.yaml`
lets esbuild and workerd run their install scripts, which the test host needs.
File renamed without changes.
1 change: 0 additions & 1 deletion CLAUDE.md

This file was deleted.

Loading
Loading