feat(plugin-cli): sandboxed plugin authoring CLI#1057
Conversation
…ract
First phase of the sandboxed plugin redesign (#1028b). Adds the
manifest fields that make `src/index.ts` and the in-code descriptor
factory redundant. The trust contract is now hand-authored in the
manifest, where a security reviewer can find it without grep.
New required fields:
- `slug`: ASCII letter then letters/digits/hyphens/underscores, max 64
chars. Matches the registry lexicon's rkey grammar via the shared
PLUGIN_SLUG_RE in @emdash-cms/plugin-types.
- `version`: semver 2.0 subset, no build-metadata (atproto rkeys can't
contain `+`). Validated via PLUGIN_VERSION_RE.
- `publisher`: now required (was optional in #1028a). The runtime
cannot compute the plugin's AT URI without it; making it optional
meant the plugin couldn't load locally before first publish.
New optional fields with sensible defaults:
- `capabilities`: array of capability strings. Defaults to []. Each
entry validated against the current vocabulary; deprecated names are
hard-rejected with a hint at the replacement (no deprecation window
for new authoring).
- `allowedHosts`: array of host patterns. Defaults to []. Required
non-empty when `network:request` is declared without
`:unrestricted`. Forbidden when `:unrestricted` is declared.
- `storage`: map of collection name -> { indexes, uniqueIndexes? }.
Defaults to {}.
The cross-field rule for network:request / allowedHosts mirrors the
release-extension lexicon's networkRequestConstraints behaviour, so
authors hit the schema error here rather than a PDS validation error
at publish time.
Schema regenerated. 33 new tests; 204 total passing.
Part of #1028b. The bundle rewrite, init command, plugin migrations,
and `localPlugin` dev helper land in subsequent commits.
Second phase of the sandboxed plugin redesign (#1028b). Bundle no longer imports src/index.ts for a descriptor factory; the manifest is the source of truth for identity (slug, version) and the trust contract (capabilities, allowedHosts, storage). Bundle still probes the runtime code for the hook/route surface — that's a syntactic property that needs the code to exist. Changes to bundle: - Drop the main-entry build and descriptor extraction. No more src/index.ts probing, no more `createPlugin` / default-factory / default-object format detection. - Replace `resolveEntries`: just locates emdash-plugin.jsonc (loaded through the same loader the CLI's validate uses) and confirms src/plugin.ts exists. No more package.json `exports` parsing. - Replace `extractResolvedPlugin` with `assembleResolvedPlugin`: builds the ResolvedPlugin shape from the manifest, then probes src/plugin.ts for hook/route names. - Probe (renamed from `augmentWithSandboxProbe` to `probePluginSurface`) now reads src/plugin.ts. Hard-fails if the default export isn't a definePlugin result. - New error codes: MISSING_MANIFEST, MISSING_PLUGIN_ENTRY, MANIFEST_INVALID. Old MISSING_PACKAGE_JSON / MISSING_ENTRYPOINT / MAIN_BUILD_FAILED gone. - Admin entry handling (admin.js, adminPages, adminWidgets) deferred to a follow-up issue. The redesign hasn't touched admin yet; that surface stays as-is and is gated on the descriptor's `admin` field which no longer exists. When admin lands again it'll be a manifest field with its own probe. Changes to translate.ts: - `NormalisedManifest` gains slug, version, publisher (required), capabilities, allowedHosts, storage. Publisher is no longer Optional — the schema enforces it. Fixtures: - `minimal-plugin/`: src/index.ts gone, sandbox-entry.ts renamed to plugin.ts, new emdash-plugin.jsonc with identity + trust contract. - `bad-plugin/`: stripped to manifest-only (no src/), exercises MISSING_PLUGIN_ENTRY. Old "declares hooks but no sandbox entry" case isn't possible anymore — there's no descriptor declaring anything. Net diff: -228 lines.
Third phase of the redesign (#1028b). Adds `emdash-registry init [name]` which produces the three-file plugin layout introduced by the previous commits: emdash-plugin.jsonc, src/plugin.ts, package.json, plus a tsconfig, README, .gitignore, and a passing test. Modes: - Interactive (default on a TTY): clack prompts for each unset field with sensible defaults. ESC / Ctrl+C cancels cleanly. - `--yes` / `-y` (non-interactive): no prompts; unset fields become TODO placeholders in the manifest. The author fixes them before first use. - Non-TTY (CI, pipes): same as `--yes`; prompting into a non- interactive stdin would hang. Pre-fills: - Publisher: the active session's handle from FileCredentialStore. Resolved through @atcute/identity-resolver to a DID before write so the runtime never sees a mutable handle. The handle is emitted as a `// <handle>` line comment next to the pinned DID for `git diff` readability — same convention as the post-publish write-back. - Author name / email: `git config user.name` / `user.email`. - Repo: `git remote get-url origin`, normalised from SSH to https (`git@github.com:foo/bar.git` → `https://github.com/foo/bar`). Falls back to `package.json#repository.url` if no git remote. - License, description: `package.json` in the target dir if one exists (for the "scaffold into existing repo skeleton" case). Slug defaults to the positional `name`, `basename(--dir)`, or basename(cwd) in that order. Every flag is optional in every mode. Exported `resolveHandleToDid` from manifest/publisher.ts so init can use the same resolver the post-publish write-back does. Tests: 44 new (template renderers, scaffold filesystem behaviour, environment probe). 249 total in the package.
Fourth phase of the redesign (#1028b). Moves the 5 in-tree sandboxed plugins to the manifest + src/plugin.ts shape so they become the canonical references a plugin author looks at. Each plugin's layout changes from: src/index.ts (descriptor factory, ~50 lines) src/sandbox-entry.ts (runtime code via definePlugin) package.json (main / exports / files / build scripts) to: emdash-plugin.jsonc (identity + trust contract + admin surface) src/plugin.ts (runtime code, unchanged) package.json (private, typecheck script only) Plugins migrated: - atproto - audit-log - marketplace-test - sandboxed-test - webhook-notifier Schema gains `admin` (pages + widgets) since four of the five plugins declare admin surface. Mirrors PluginAdminPage / PluginDashboardWidget in core. Atproto's plugin.test.ts rewritten to assert against the manifest instead of the deleted descriptor factory. KNOWN BREAKAGE: demos that import the old factories (`auditLogPlugin()`, `webhookNotifierPlugin()`) from astro.config.mjs are broken until the next commit ships `@emdash-cms/registry-cli/dev`'s `localPlugin(dir)` helper and updates the demos. All published plugins still work — the bundled manifest.json shape is unchanged. Only authoring changed.
Final piece of the sandboxed-plugin redesign (#1028b). Closes the gap
the plugin migrations opened — demos that previously imported
`auditLogPlugin()` / `webhookNotifierPlugin()` factories now consume
the plugins through their source directories.
New subpath `@emdash-cms/registry-cli/dev` exports `localPlugin(dir)`,
which:
- Reads `<dir>/emdash-plugin.jsonc` via the same loader the CLI uses.
- Confirms `<dir>/src/plugin.ts` exists.
- Resolves the manifest's publisher (handle → DID) so the descriptor
is in canonical form.
- Returns a PluginDescriptor-shaped object with `entrypoint` set to
the absolute `file://` URL of `src/plugin.ts`. Vite resolves the
URL through its standard fs path resolver — no build step needed.
The descriptor carries id, version, capabilities, allowedHosts,
storage, and (when declared) adminPages + adminWidgets from the
manifest. Plugins that don't expose admin surface pass through
without the optional fields, keeping the descriptor tidy.
Demos updated:
- demos/simple: auditLogPlugin() → localPlugin("../../packages/plugins/audit-log")
- demos/plugins-demo: auditLog + webhookNotifier the same way
- demos/cloudflare: webhookNotifier via localPlugin
- infra/cache-demo, infra/blog-demo: same
Trusted plugins (formsPlugin, embedsPlugin, apiTestPlugin) keep their
factory-based imports — they're not on the new shape and aren't part
of this redesign's scope.
Errors surface as a structured LocalPluginError with codes:
- MANIFEST_INVALID
- PLUGIN_ENTRY_MISSING
- PUBLISHER_UNRESOLVED
Tests: 10 new (descriptor shape, error paths, admin pass-through).
259 total in the package.
Renames @emdash-cms/registry-cli to @emdash-cms/plugin-cli and the
binary emdash-registry to emdash-plugin. Adds build + dev commands,
consolidates the build pipeline so bundle is a thin packaging step on
top of build. Introduces a strict author-facing SandboxedPlugin type
via the new emdash/plugin type-only subpath; sandboxed plugins now
default-export a bare { hooks?, routes? } object with satisfies
SandboxedPlugin and have no runtime emdash import. Drops definePlugin
and the build shim for sandboxed plugins (definePlugin is native-only
now). Migrates the five in-tree sandboxed plugins to the new shape.
Manifest version is optional and reconciled with package.json#version.
- init scaffold emits the new `satisfies SandboxedPlugin` shape and
npm-shape package.json (build/dev scripts, ./sandbox export, plugin-cli
devDep) instead of the broken `definePlugin` template
- publish reads package.json#version and reconciles via normaliseManifest
so the new "version in package.json only" pattern actually publishes;
malformed package.json surfaces a CliError, not a misleading
VERSION_MISSING further down
- dev watcher serialises rebuilds (queue collapsed to one follow-up),
closes the watcher before draining pending on Ctrl-C, short-circuits
scheduleRebuild during shutdown, handles Windows path separators in
the outDir ignore glob, clears pending+queuedTrigger in finally so an
IIFE rejection can't deadlock the session, and removes SIGINT handlers
on shutdown
- adapter normalises ctx.request to SandboxedRequest shape in-process
so handlers see the same { url, method, headers: Record } promised by
the strict type; null/array/non-object default exports rejected with
a plugin-id-bearing message
- build's readPackageMeta rejects empty/non-string version with the
same strictness as publish, killing the build-pass/publish-fail
asymmetry
- pipeline probe rejects invalid hook config (errorPolicy, priority,
timeout) so untyped JS authors get a build error rather than a
silently-wrong runtime contract
- versionless minimal-plugin fixture so bundle/publish/build integration
tests exercise the package.json-as-source-of-truth path
- definePlugin error wording softened for native-plugin authors whose
id field has a typo
- pipeline error messages and stale comments updated for the no-shim,
no-definePlugin authoring shape
- removed dead EMDASH_SHIM from the Cloudflare sandbox runner
- changesets retargeted to @emdash-cms/plugin-cli; scaffold/atproto/core
comments scrubbed for stale registry-cli references
🦋 Changeset detectedLatest commit: 7ffc93c The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 7ffc93c | May 18 2026, 11:29 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-i18n | 7ffc93c | May 18 2026, 11:28 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | 7ffc93c | May 18 2026, 11:29 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-perf-coordinator | 7ffc93c | May 18 2026, 11:28 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 7ffc93c | May 18 2026, 11:30 AM |
Scope checkThis PR changes 6,993 lines across 100 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
There was a problem hiding this comment.
Pull request overview
Renames @emdash-cms/registry-cli → @emdash-cms/plugin-cli (binary emdash-registry → emdash-plugin), introduces a new sandboxed plugin authoring shape (satisfies SandboxedPlugin from emdash/plugin), and consolidates the plugin build pipeline under a new build/dev/bundle CLI surface. The visible diffs in this slice are mostly downstream updates: in-tree templates and demo astro.config.mjs files switch from factory imports (auditLogPlugin(), webhookNotifierPlugin()) to default-export instances, agent-skill docs update their snippets to match, the workspace catalog adds chokidar and drops the Zod pin comment, and several files in packages/registry-client plus the now-deleted packages/registry-cli fixtures get their textual emdash-registry references retargeted to emdash-plugin.
Changes:
- Template + demo configs and agent-skill documentation updated for the new default-export plugin shape.
packages/registry-clientdoc/comment/error-message references updated fromemdash-registry/@emdash-cms/registry-clitoemdash-plugin/@emdash-cms/plugin-cli.pnpm-workspace.yamlcatalog addschokidar ^5.0.0(used by the newdevwatcher), re-sortsjsonc-parser, and removes the Zod-pin rationale comment (pin itself remains).
Reviewed changes
Copilot reviewed 125 out of 147 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
templates/*/.agents/skills/building-emdash-site/references/configuration.md (×8) |
Update docs snippet from auditLogPlugin() factory to auditLog default export. |
| templates/blog/astro.config.mjs | Switch audit-log plugin to default-export usage. |
| templates/blog-cloudflare/astro.config.mjs | Switch webhook-notifier sandboxed plugin to default-export usage. |
| pnpm-workspace.yaml | Add chokidar ^5.0.0, reorder jsonc-parser, drop Zod-pin comment. |
| packages/registry-client/src/index.ts, publishing/index.ts, credentials/types.ts, credentials/file.ts, README.md | Rename CLI references from emdash-registry/@emdash-cms/registry-cli to emdash-plugin/@emdash-cms/plugin-cli. |
| packages/registry-cli/tests/fixtures/{minimal-plugin,bad-plugin}/src/*.ts | Delete obsolete fixture files (registry-cli package removed). |
| packages/registry-cli/src/manifest/translate.ts | Delete obsolete translate module. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
| "INVALID_PLUGIN_FORMAT", | ||
| `${entries.pluginEntry} must default-export an object with \`hooks\` and/or \`routes\` (sandboxed plugin shape: \`export default { hooks, routes } satisfies SandboxedPlugin\` from "emdash/plugin"). Got ${describeShape(definition)}.`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
BUG: Missing default export silently produces an empty plugin
Category: Error Handling / Data Integrity
Severity: HIGH
(pluginModule.default ?? {}) substitutes an empty object when default is undefined. The shape check on the next line (typeof === "object" && !null && !Array.isArray) passes for {}, so the INVALID_PLUGIN_FORMAT guard never fires. hooks and routes then read as undefined and the loops skip silently. buildPlugin returns success, writes dist/manifest.json with empty hooks: [] / routes: [], and emits a runtime plugin.mjs. The author sees a green build for a plugin that does nothing.
Trigger: write src/plugin.ts as export const plugin = { ... } satisfies SandboxedPlugin (forgot default), or export { plugin as default } satisfies ... mistyped, or any source where the bundler tree-shakes the default away. pnpm build succeeds; the bundle ships and no hook ever fires.
Note the error message at line 303 already says "must default-export an object" — the check just doesn't run for the missing-default case.
Fix: detect the missing default explicitly before the falsy coalesce, e.g.
if (!("default" in pluginModule)) {
throw new BuildPipelineError(
"INVALID_PLUGIN_FORMAT",
`${entries.pluginEntry} has no \`default\` export. ...`,
);
}
const definition = pluginModule.default;or fold the missing case into the existing INVALID_PLUGIN_FORMAT branch by checking pluginModule.default === undefined first.
|
Review posted successfully. Summary of what I posted to PR #1057: Two findings, line-anchored:
|
- Delete `packages/marketplace/tests/publish-e2e.test.ts` — invoked the legacy `emdash plugin bundle` from core CLI against the new manifest-driven plugin layout, which it doesn't understand. - Remove the validate-plugins CI job — it used the same legacy CLI command. Plugin validation is now covered by `pnpm build`, which runs the new `emdash-plugin build` probe + manifest checks against every in-tree sandboxed plugin. - Fix `no-base-to-string` lint errors in audit-log/plugin.ts. The canonical ContentHookEvent types `event.content.id` as unknown; `String(unknown)` lands on '[object Object]' for record IDs. Added a small `stringifyId` helper that returns '' for non-string/number inputs so the caller's existence check skips bad rows. - pipeline.ts now hard-errors when the probed module has no `default` export, instead of silently falling through to an empty plugin (build had been writing dist/ artifacts with empty hooks/routes for any source that used `export const plugin = ...`). - Scaffold README camelCases hyphenated slugs for the import binding. Slugs like `my-plugin` were producing `import my-plugin from ...` which is a syntax error. Test added with a hyphenated fixture. Both bot review comments addressed.
bundle.test.ts > 'produces a tarball + manifest for a minimal valid plugin' timed out at the 5s default on the GitHub-hosted runner. The test runs the full build pipeline (tsdown probe + transpile + tarball pack), which is fast locally (<2s) but cold-starts at 5-8s on CI. Bump to 30s globally for the plugin-cli vitest config.
|
@ask-bonk merge main and update the lockfile |
|
To https://github.com/emdash-cms/emdash.git |
|
@ascorbic Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: ascorbic <213306+ascorbic@users.noreply.github.com>
Resolved and pushed in Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@ask-bonk kimi update the lockfile |
|
Done. The lockfile has been regenerated to account for the |
|
I have read the CLA Document and I hereby sign the CLA 1 out of 2 committers have signed the CLA. |
* docs(plugins): document the plugin CLI, manifest, and authoring-shape changes (#1040, #1057) Adds migration guides for site operators and plugin authors, new reference pages for emdash-plugin.jsonc and the emdash-plugin CLI, and rewrites the sandboxed-plugin guides to the new default-export shape. Migration guides follow Astro's breaking-changes format; Atmosphere account terminology used throughout. * docs(plugins): apply definition-by-negation / builder-salience standard Self-review of #1059 against the standards formalized since it was written. Removed definition-by-negation and bundle-internal framing from the guide pages (migration guides left as-is — comparison/changelog is their purpose): - 'You do not write a descriptor or a build script' -> dropped (the positive sentence already says what build does) - 'the build produces both, so you never hand-write either' -> 'generates both' - 'type-only, so the emdash runtime does not enter the plugin bundle' / 'the bundler erases them — no emdash runtime enters' -> 'provides only types, so a sandboxed plugin has no runtime dependency on emdash' - 'you never write it by hand' -> 'EmDash derives ... automatically' - 'init never requires extra flags to succeed' -> 'A slug is the only required input' - 'wire-side filename' -> '(the filename the registry expects)' - 'The registry never stores your plugin's code' lead -> dropped; the positive 'you host the tarball; registry stores a link' carries it Tropes scan clean; em-dashes appositive; bold bullets definitional. * docs(plugins): address review feedback on #1059 - Manifest profile prose lists author and security contact as required, matching the table and the actual ManifestSchema (.refine() rules in packages/plugin-cli/src/manifest/schema.ts require one of author/ authors and one of security/securityContacts). - Publishing prerequisites match the same required-fields story. - Bundle flag documentation uses kebab-case (--out-dir, --validate-only) to match the post-rename CLI (#1091). - your-first-plugin scaffolding example bumps emdash to >=0.13.0 (the first release exposing the emdash/plugin entrypoint) and pins @emdash-cms/plugin-cli to 0.2.0 (exact pin per the publishing page's experimental-registry guidance). * docs(plugins): address second round of review feedback on #1059 - api-routes: clarify that the route URL segment is the plugin's slug (the same value as ctx.plugin.id at runtime) — previously phrased as <plugin-id> without explaining what that is. - api-routes: SandboxedRouteContext is non-generic in emdash/plugin (input is unknown; authors narrow with a route-level Zod schema). The earlier reference snippet showed a TInput generic that doesn't exist in the exported type. - cli: --json is supported by the non-interactive output commands (whoami, validate, search, info, login, publish), not all commands — logout, switch, init, build, dev, bundle do not define a json arg. * docs(plugins): align discovery flag and prose on registry terminology The CLI flag for overriding the discovery endpoint is being renamed from --aggregator to --registry-url in #1092, matching the EMDASH_REGISTRY_URL env var and the user-facing concept of a registry. Update the docs to use the new flag name and drop the user-facing mention of 'aggregator' in publishing.mdx in favour of 'registry'.
Catches deployment/suhail-ski up with emdash-cms/emdash main. Conflict in pnpm-lock.yaml resolved by taking upstream's lock and re-running pnpm install to pick up the @suhailski/57th-parallel template deps. Notable upstream changes folded in: - Plugin authoring CLI + manifest format (emdash-cms#1057, emdash-cms#1059) - Registry packages — lexicons, client, CLI (emdash-cms#923) - Migration 036 taxonomy preservation (emdash-cms#1086) - WXR import w/ WPML/Polylang translations (emdash-cms#1087) - TypeScript 6 upgrade, tsgo beta (emdash-cms#1074) - FTS5 corruption fix on publish (emdash-cms#768) - Plugin bundle size caps (emdash-cms#978) - And ~280 smaller fixes / chores CLI rebuilt against the new core. Verified by listing posts on suhail.ski — both tutorial posts come back as expected. 57th-parallel template untouched by upstream. Our 5 local commits preserved across the merge. Used --config.manage-package-manager-versions=false to bypass pnpm's self-switch to 11.1.3, which packages weirdly on Windows. The current shell's pnpm 10.12.4 worked fine against the merged manifests.
What does this PR do?
Reworks how sandboxed plugins are authored, built, and distributed. Three threads, one PR because they all touch the same plugin surface and changesetting them together is the only way to publish a coherent release.
New authoring shape. Sandboxed plugins now
export default { hooks, routes } satisfies SandboxedPluginfromemdash/plugin. No moredefinePluginwrapper, no more runtimeemdashimport in plugin source. The new strict mappedSandboxedPlugintype gives full per-hook event + return type inference automatically.definePluginis now native-only (throws with a pointer to the new shape if called withoutid).emdash-plugin build+emdash-plugin dev. The CLI's build pipeline is consolidated underpackages/plugin-cli/src/build/.buildreadsemdash-plugin.jsonc+src/plugin.ts+ optionalpackage.jsonand emitsdist/plugin.mjs(runtime bytes),dist/manifest.json(wire-shapePluginManifestwith hooks/routes harvested from probing), anddist/index.mjs(bare descriptor module).devis a chokidar-based watcher around the same pipeline with serialised rebuilds, last-good-on-failure semantics, and clean Ctrl-C drain.bundleis now a thin packaging step on top ofbuild(rename plugin.mjs → backend.js inside the tarball only). Optional manifestversionis reconciled withpackage.json#version(resolvePluginVersion).Package rename.
@emdash-cms/registry-cli→@emdash-cms/plugin-cli; binaryemdash-registry→emdash-plugin. The package's job has outgrown the original name —init,build,dev,bundle,publish, plus discovery + identity. The old package is no longer published.All 5 in-tree plugins migrated to the new shape. Schema regenerated. 4 changesets in
.changeset/.Closes: see Discussion #296
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI-generated code disclosure
Notes
Breaking changes
@emdash-cms/registry-cliis renamed to@emdash-cms/plugin-cliand the binaryemdash-registryis nowemdash-plugin. Old name stops getting published; users update their devDeps and scripts.@emdash-cms/plugin-audit-log,@emdash-cms/plugin-webhook-notifier,@emdash-cms/plugin-atprotodrop their named exports and factory call shape. Sites using them switch fromimport { auditLogPlugin } from "..."; plugins: [auditLogPlugin()]toimport auditLog from "..."; plugins: [auditLog]. Per-plugin changesets document the migration.emdashremoves thedefinePluginstandard-format overload and theStandardPluginDefinition/isStandardPluginDefinitionexports. Sandboxed plugins move tosatisfies SandboxedPluginfromemdash/plugin. The runtime-side handle type formerly calledSandboxedPluginis renamed toSandboxedPluginInstanceto free the name for the new author-facing type — relevant for anyone implementing a customSandboxRunner(e.g.@emdash-cms/cloudflarewas updated in this PR).Authoring shape
Plugin
package.jsonscripts{ "scripts": { "build": "emdash-plugin build", "dev": "emdash-plugin dev" } }Adversarial review
Four rounds (12 → 4 → 2 → 0 substantive findings). Notable fixes from review:
definePlugintemplate)package.json#versionand reconciles vianormaliseManifest(was failing for the recommended manifest-version-omitted pattern)Requestto the strictSandboxedRequestshape so handlers behave identically in-process and in-isolatereadPackageMetaaligned with publish's strictness on malformedpackage.json#versionVerification
emdash-plugin build