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
154 lines
6.1 KiB
Markdown
154 lines
6.1 KiB
Markdown
# @emdash-cms/plugin-cli
|
|
|
|
CLI for authoring, building, and publishing EmDash plugins.
|
|
|
|
> EXPERIMENTAL: `init`, `build`, `dev`, `bundle`, `login`, `whoami`, `switch`, and `publish` all work today against any atproto PDS — `publish` writes profile + release records to the publisher's own repo. The discovery commands (`search`, `info`) need an aggregator; the experimental aggregator is at `registry.emdashcms.com`. NSIDs and shapes will change while RFC 0001 is in flight; pin to an exact version.
|
|
|
|
## Installation
|
|
|
|
```sh
|
|
npx @emdash-cms/plugin-cli init my-plugin
|
|
```
|
|
|
|
Or install globally:
|
|
|
|
```sh
|
|
npm install -g @emdash-cms/plugin-cli
|
|
emdash-plugin init my-plugin
|
|
```
|
|
|
|
## Commands
|
|
|
|
```text
|
|
emdash-plugin init [name] Scaffold a new sandboxed plugin
|
|
emdash-plugin build Build dist/ artifacts (plugin.mjs, manifest.json, index.mjs)
|
|
emdash-plugin dev Watch sources and rebuild on change
|
|
emdash-plugin bundle Pack dist/ + assets into a registry tarball
|
|
emdash-plugin publish --url <url> Publish a release that points at a hosted tarball
|
|
emdash-plugin validate [path] Validate emdash-plugin.jsonc against the v1 schema
|
|
emdash-plugin login <handle-or-did> Interactive atproto OAuth login
|
|
emdash-plugin logout [--did <did>] Revoke the active session
|
|
emdash-plugin whoami Show stored sessions
|
|
emdash-plugin switch <did> Switch the active publisher session
|
|
emdash-plugin search <query> Free-text search
|
|
emdash-plugin info <handle-or-did> <slug> Show package details
|
|
```
|
|
|
|
The non-interactive output commands (`whoami`, `validate`, `search`, `info`, `login`, `publish`) accept `--json` for machine-readable output. Discovery commands (`search`, `info`) accept `--registry-url <url>` (or `EMDASH_REGISTRY_URL`).
|
|
|
|
## Development
|
|
|
|
From a fresh checkout, run `pnpm install` and then `pnpm build` before a scoped package typecheck:
|
|
|
|
```sh
|
|
pnpm install
|
|
pnpm build
|
|
pnpm --filter @emdash-cms/plugin-cli typecheck
|
|
```
|
|
|
|
The build emits internal workspace type declarations used by the scoped typecheck, matching CI's build-then-typecheck order.
|
|
|
|
## Authoring
|
|
|
|
A typical plugin's `package.json` scripts:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build": "emdash-plugin build",
|
|
"dev": "emdash-plugin dev"
|
|
}
|
|
}
|
|
```
|
|
|
|
The plugin author writes two files:
|
|
|
|
- `emdash-plugin.jsonc` — identity (slug, publisher) + trust contract (capabilities, allowedHosts, storage) + profile fields.
|
|
- `src/plugin.ts` — runtime behaviour (hooks + routes), with `export default { ... } satisfies SandboxedPlugin` from `emdash/plugin`.
|
|
|
|
`emdash-plugin build` produces:
|
|
|
|
- `dist/plugin.mjs` (+ `dist/plugin.d.mts`) — runtime bytes the integration loads (in-process or in a sandbox isolate).
|
|
- `dist/manifest.json` — wire-shape manifest including the hooks + routes harvested from probing `src/plugin.ts`.
|
|
- `dist/index.mjs` (+ `dist/index.d.mts`) — descriptor module that default-exports a bare `PluginDescriptor`. Consumers import this directly.
|
|
|
|
## Publishing
|
|
|
|
Three steps. The CLI does not host artifacts — you do, anywhere public.
|
|
|
|
```sh
|
|
emdash-plugin bundle
|
|
# upload dist/<id>-<version>.tar.gz somewhere public
|
|
emdash-plugin publish --url https://example.com/foo-1.0.0.tar.gz
|
|
```
|
|
|
|
On first publish, pass `--license` and `--security-email` (or `--security-url`) to bootstrap the package profile — or keep them in `emdash-plugin.jsonc` (see below).
|
|
|
|
## `emdash-plugin.jsonc`
|
|
|
|
Drop an `emdash-plugin.jsonc` file next to your plugin's `package.json`. The CLI reads it automatically from the current directory. Schema-driven IDE completion works via the bundled JSON Schema:
|
|
|
|
```jsonc
|
|
{
|
|
"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
|
|
|
|
"slug": "gallery",
|
|
"publisher": "did:plc:abc123def456",
|
|
|
|
"license": "MIT",
|
|
"author": { "name": "Jane Doe", "url": "https://example.com" },
|
|
"security": { "email": "security@example.com" },
|
|
|
|
// Optional
|
|
"name": "Gallery",
|
|
"description": "Image gallery block for EmDash.",
|
|
"keywords": ["gallery", "images"],
|
|
"repo": "https://github.com/example/plugin-gallery",
|
|
|
|
// Trust contract
|
|
"capabilities": ["content:read"],
|
|
"allowedHosts": [],
|
|
"storage": {},
|
|
}
|
|
```
|
|
|
|
The file is JSONC: comments and trailing commas are allowed. Use `authors: [...]` and `securityContacts: [...]` for multi-author or multi-contact plugins. `version` is optional — when omitted, the CLI reads `version` from the adjacent `package.json`.
|
|
|
|
### Publisher pinning
|
|
|
|
After your first successful publish, the CLI writes the active session's DID back into the manifest as `publisher`:
|
|
|
|
```jsonc
|
|
{
|
|
"license": "MIT",
|
|
"publisher": "did:plc:abc123def456",
|
|
...
|
|
}
|
|
```
|
|
|
|
On every subsequent publish, the CLI verifies the active session matches the pinned `publisher`. If they don't match, publish refuses with `MANIFEST_PUBLISHER_MISMATCH` so you can't accidentally publish under the wrong account. To resolve a mismatch, either:
|
|
|
|
- switch sessions: `emdash-plugin switch <did>`
|
|
- update the manifest if you're transferring the plugin to a new publisher
|
|
|
|
**DIDs are the identity, not handles.** Internally the CLI always compares the active session's DID against the pinned publisher's DID. If you pin a handle (`"publisher": "example.com"`), the CLI resolves it to a DID at publish time and compares against that — so a handle pin is just a friendlier alias for the underlying DID. Handles are mutable: if the publisher's domain changes ownership and the resolver later points at a different DID, the publish will refuse. DIDs are durable and the recommended pin for long-lived plugins.
|
|
|
|
Validate without publishing:
|
|
|
|
```sh
|
|
emdash-plugin validate
|
|
```
|
|
|
|
CLI flags (`--license`, `--author-name`, …) still win over manifest values when both are set, which is useful in CI. Pass `--no-manifest` to skip the manifest entirely.
|
|
|
|
## Programmatic API
|
|
|
|
```ts
|
|
import { buildPlugin, bundlePlugin } from "@emdash-cms/plugin-cli";
|
|
|
|
await buildPlugin({ dir: "./my-plugin" });
|
|
const result = await bundlePlugin({ dir: "./my-plugin" });
|
|
```
|
|
|
|
For discovery and credentials, import from `@emdash-cms/registry-client`.
|