Jedes sandboxed Plugin hat eine emdash-plugin.jsonc neben seiner package.json. Sie wird manuell bearbeitet und enthält die Identität des Plugins, seinen Vertrauensvertrag (Capabilities, Hosts, Storage) und die Profilfelder, die die Registry anzeigt. emdash-plugin init erstellt eine; die CLI liest ./emdash-plugin.jsonc automatisch für build, dev, validate, bundle und publish.
Die Datei ist JSONC: Kommentare und abschließende Kommas sind erlaubt.
Das folgende Beispiel zeigt ein vollständiges Manifest für ein Bildergalerie-Plugin:
{
"$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 profile
"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": {}
}
Identität
| Feld | Erforderlich | Hinweise |
|---|---|---|
slug | Ja | URL-sichere ID innerhalb des Publisher-Namespace. /^[a-z][a-z0-9_-]*$/, max. 64 Zeichen. |
publisher | Ja | Die DID oder Handle Ihres Atmosphere-Kontos. Siehe Publisher-Pinning. |
version | Nein | Semver 2.0 ohne Build-Metadaten. Normalerweise weglassen — siehe unten. |
slug und publisher zusammen bilden die Identität des Pakets. EmDash leitet die vollständige Kennung des Pakets automatisch daraus ab.
version befindet sich in package.json
Der Build gleicht die version des Manifests mit package.json#version ab:
- Beide gesetzt und gleich → in Ordnung.
- Beide gesetzt und unterschiedlich → harter Fehler.
- Eines gesetzt → dieser Wert gewinnt.
- Keines gesetzt → harter Fehler.
Das empfohlene Muster für ein npm-verteiltes Plugin ist, version aus dem Manifest wegzulassen und package.json als einzige Quelle der Wahrheit zu verwenden (Ihr Release-Tooling erhöht sie dort bereits). Nur-Registry-Plugins ohne package.json müssen version im Manifest setzen — es gibt keinen anderen Ort dafür.
Profil
Diese speisen die Registry-Auflistung. license, ein Autor (author oder authors) und ein Sicherheitskontakt (security oder securityContacts) sind erforderlich; der Rest ist optional.
| Feld | Erforderlich | Hinweise |
|---|---|---|
license | Ja | SPDX-Ausdruck ("MIT", "Apache-2.0", "MIT OR Apache-2.0"). Wird bei der ersten Veröffentlichung verwendet; das bestehende Profil gewinnt bei späteren Veröffentlichungen. |
author / authors | Ja | Eines von beiden. author: { name, url?, email? } für einen einzelnen Autor; authors: [...] (≤ 32) für mehrere. Beide zu setzen ist ein Fehler. |
security / securityContacts | Ja | Eines von beiden. Jeder Kontakt benötigt mindestens email oder url. securityContacts: [...] (≤ 8) für mehrere. Beide zu setzen ist ein Fehler. |
name | Nein | Anzeigename. Standardmäßig der Slug. |
description | Nein | Halten Sie es kurz (etwa 140 Zeichen). Lange Werte können in Listen abgeschnitten werden. |
keywords | Nein | ≤ 5 Einträge. |
repo | Nein | https://-URL des Quell-Repos. |
Verwenden Sie die Singular-Form author / security, es sei denn, Sie haben wirklich mehrere — das ist der häufigste Fall und das Scaffold gibt sie aus.
Vertrauensvertrag
Der Vertrauensvertrag besteht aus capabilities, allowedHosts und storage. Alle drei sind standardmäßig leer, sodass ein Plugin, das keine zusätzlichen Berechtigungen benötigt, sie vollständig weglassen kann.
{
"capabilities": ["network:request", "content:read"],
"allowedHosts": ["api.example.com", "*.cdn.example.com"],
"storage": {
"events": { "indexes": ["timestamp"] },
"submissions": { "indexes": ["email"], "uniqueIndexes": ["token"] }
}
}
Capabilities
Die anerkannten Namen:
| Capability | Gewährt |
|---|---|
content:read / content:write | Site-Inhalte über ctx lesen / ändern. |
media:read / media:write | Medien lesen / schreiben. |
users:read | Benutzerdatensätze lesen. |
email:send | E-Mail über ctx senden. |
network:request | Ausgehende HTTP-Anfragen über ctx.http, beschränkt auf allowedHosts. |
network:request:unrestricted | Ausgehende HTTP-Anfragen an jeden Host. Wird anstelle von network:request verwendet. |
hooks.email-transport:register | Einen E-Mail-Transport-Hook registrieren. |
hooks.email-events:register | E-Mail-Lebenszyklus-Hooks registrieren. |
hooks.page-fragments:register | Einen page:fragments-Hook registrieren (nur nativ). |
Zwei feldübergreifende Regeln, die die CLI durchsetzt (die JSON-Schema-Prüfung des Editors nicht — führen Sie emdash-plugin validate aus):
network:requesterfordert ein nicht-leeresallowedHosts. Wenn das Plugin wirklich jeden Host erreichen muss, verwenden Sie stattdessennetwork:request:unrestricted.network:request:unrestrictederfordert, dassallowedHostsleer ist — die uneingeschränkte Capability gewährt bereits jeden Host, sodass eine Liste ihr widersprechen würde.
Host-Muster sind reine Hostnamen (kein Schema, Pfad oder Leerzeichen). Ein führendes *. erlaubt Subdomains: *.cdn.example.com.
Storage
Eine Zuordnung von Sammlungsname → Index-Konfiguration. Sammlungsnamen folgen der gleichen /^[a-z][a-z0-9_]*$/-Regel (die Laufzeit verwendet den Namen als SQL-Tabellen-Suffix). Indizes sind Feldnamen oder zusammengesetzte Arrays; uniqueIndexes sind auch abfragbar — listen Sie sie nicht auch in indexes auf.
"storage": {
"events": { "indexes": ["timestamp", ["collection", "timestamp"]] }
}
Admin-Oberfläche
Optional. Sandboxed Plugins rendern Admin-Seiten und Dashboard-Widgets über Block Kit; das Manifest deklariert nur, wo sie erscheinen. Lassen Sie den admin-Schlüssel vollständig weg, wenn das Plugin keine Admin-UI hat.
"admin": {
"pages": [{ "path": "/gallery", "label": "Gallery", "icon": "image" }],
"widgets": [{ "id": "recent-uploads", "title": "Recent uploads", "size": "half" }]
}
Ein Plugin, das admin.pages oder admin.widgets deklariert, muss auch eine admin-Route in src/plugin.ts bereitstellen, die den Block Kit-Inhalt rendert — das Schema kann das nicht durchsetzen (Routennamen werden aus der Quelle, nicht aus dem Manifest, ermittelt), aber die Laufzeit prüft es.
Publisher-Pinning
publisher fixiert die Publishing-Identität, damit Sie nicht versehentlich ein Plugin unter dem falschen Konto veröffentlichen können.
Bei Ihrem ersten erfolgreichen Publish, wenn der publisher des Manifests mit der aktiven Sitzung übereinstimmt, bleibt er wie geschrieben. Wenn Sie mit emdash-plugin init ein Scaffold erstellt und es leer gelassen haben, schreibt die CLI die DID der aktiven Sitzung zurück ins Manifest.
Das folgende Beispiel zeigt die Zeile, die die CLI schreibt, mit dem aufgelösten Handle als Kommentar für bessere Lesbarkeit:
"publisher": "did:plc:abc123def456", // jane.example.com
Bei jedem nachfolgenden Publish löst die CLI die aktive Sitzung und den fixierten publisher zu DIDs auf und vergleicht sie. Eine Nichtübereinstimmung schlägt sofort mit MANIFEST_PUBLISHER_MISMATCH fehl — es gibt kein Override-Flag. Lösen Sie es bewusst:
- Falsche Sitzung:
emdash-plugin switch <did>, dann erneut veröffentlichen. - Echte Übertragung des Plugins an einen neuen Publisher:
publisherim Manifest bearbeiten.
Ohne Veröffentlichung validieren
emdash-plugin validate # ./emdash-plugin.jsonc
emdash-plugin validate path/ # ein bestimmtes Verzeichnis
Offline-Schema-Prüfung mit tsc-artigen file:line:column-Diagnosen, einschließlich der feldübergreifenden Regeln. Geeignet für einen Pre-Commit-Hook oder CI-Schritt. Doppelte Schlüssel und unbekannte Schlüssel sind Fehler (Strict-Modus fängt "licens"-Tippfehler).
CLI-Flags gewinnen trotzdem
Explizite Flags (--license, --author-name, …) überschreiben Manifest-Werte, wenn beide gesetzt sind — nützlich für CI-Overrides. --no-manifest überspringt das Manifest vollständig (und warnt, wenn eines am Standardpfad existiert, sodass die Publisher-Pin-Sicherheitsgeschichte sichtbar bleibt).