Das Plugin-Manifest

Auf dieser Seite

Jedes Sandbox-Plugin hat eine emdash-plugin.jsonc neben seiner package.json. Sie wird manuell bearbeitet und enthält die Identität des Plugins, seinen Vertrauensvertrag (Fähigkeiten, Hosts, Speicher) und die Profilfelder, die die Registry anzeigt. emdash-plugin init erstellt ein Gerüst; 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 Image-Gallery-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": "[email protected]" },

	// Optionales Profil
	"name": "Gallery",
	"description": "Image-Gallery-Block für EmDash.",
	"keywords": ["gallery", "images"],
	"repo": "https://github.com/example/plugin-gallery",

	// Vertrauensvertrag
	"capabilities": ["content:read"],
	"allowedHosts": [],
	"storage": {}
}

Identität

FeldErforderlichHinweise
slugJaURL-sichere ID innerhalb des Publisher-Namensraums. /^[a-z][a-z0-9_-]*$/, max. 64 Zeichen.
publisherJaDie DID oder der Handle deines Atmosphere-Kontos. Siehe Publisher-Pinning.
versionNeinSemver 2.0 ohne Build-Metadaten. Normalerweise weglassen — siehe unten.

slug und publisher bilden zusammen die Identität des Pakets. EmDash leitet daraus automatisch den vollständigen Paketbezeichner ab.

version gehört 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.
  • Eine gesetzt → dieser Wert gewinnt.
  • Keine gesetzt → harter Fehler.

Das empfohlene Muster für ein npm-verteiltes Plugin ist, version im Manifest wegzulassen und package.json als einzige Quelle der Wahrheit zu verwenden (dein Release-Tooling erhöht sie bereits dort). Registry-only-Plugins ohne package.json müssen version im Manifest setzen — es gibt keinen anderen Ort dafür.

Profil

Diese fließen in den Registry-Eintrag ein. license, ein Autor (author oder authors) und ein Sicherheitskontakt (security oder securityContacts) sind erforderlich; der Rest ist optional.

FeldErforderlichHinweise
licenseJaSPDX-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 / authorsJaEines der beiden. author: { name, url?, email? } für einen einzelnen Autor; authors: [...] (≤ 32) für mehrere. Beides zu setzen ist ein Fehler.
security / securityContactsJaEines der beiden. Jeder Kontakt benötigt mindestens email oder url. securityContacts: [...] (≤ 8) für mehrere. Beides zu setzen ist ein Fehler.
nameNeinAnzeigename. Standardmäßig der Slug.
descriptionNeinHalte es kurz (ca. 140 Zeichen). Lange Werte können in Listen abgeschnitten werden.
keywordsNein≤ 5 Einträge.
repoNeinhttps://-URL des Quell-Repos.

Verwende die Singular-Form author / security, es sei denn, du hast tatsächlich mehrere — das ist der häufige Fall, und das Gerüst 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 Privilegien 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"] }
	}
}

Fähigkeiten

Die erkannten Namen:

FähigkeitGewährt
content:read / content:writeLesen / Ändern von Site-Inhalten über ctx.
taxonomies:readLesen von Taxonomie-Definitionen und Begriffen (nur lesen).
media:read / media:writeLesen / Schreiben von Medien.
users:readLesen von Benutzer-Datensätzen.
email:sendE-Mail senden über ctx.
network:requestAusgehende HTTP-Anfragen über ctx.http, beschränkt auf allowedHosts.
network:request:unrestrictedAusgehende HTTP-Anfragen an jeden Host. Wird anstelle von network:request verwendet.
hooks.email-transport:registerRegistrierung eines E-Mail-Transport-Hooks.
hooks.email-events:registerRegistrierung von E-Mail-Lebenszyklus-Hooks.
hooks.page-fragments:registerRegistrierung eines page:fragments-Hooks (nur nativ).

Zwei Kreuzfeld-Regeln, die die CLI durchsetzt (die JSON-Schema-Prüfung des Editors nicht — führe emdash-plugin validate aus):

  • network:request erfordert eine nicht-leere allowedHosts. Wenn das Plugin wirklich jeden Host erreichen muss, verwende stattdessen network:request:unrestricted.
  • network:request:unrestricted erfordert, dass allowedHosts leer ist — die uneingeschränkte Fähigkeit gewährt bereits jeden Host, sodass eine Liste widersprüchlich wäre.

Host-Muster sind blanke Hostnamen (kein Schema, Pfad oder Leerzeichen). Ein vorangestelltes *. erlaubt Subdomains: *.cdn.example.com.

Speicher

Eine Zuordnung von Sammlungsname → Indexkonfiguration. Sammlungsnamen folgen der gleichen /^[a-z][a-z0-9_]*$/-Regel (die Laufzeit verwendet den Namen als SQL-Tabellensuffix). Indexe sind Feldnamen oder zusammengesetzte Arrays; uniqueIndexes sind ebenfalls abfragbar — liste sie nicht zusätzlich in indexes.

"storage": {
	"events": { "indexes": ["timestamp", ["collection", "timestamp"]] }
}

Admin-Oberfläche

Optional. Sandbox-Plugins rendern Admin-Seiten und Dashboard-Widgets über Block Kit; das Manifest deklariert nur, wo sie erscheinen. Lasse den admin-Schlüssel ganz weg, wenn das Plugin keine Admin-UI hat.

"admin": {
	"pages": [{ "path": "/gallery", "label": "Gallery", "icon": "image" }],
	"widgets": [{ "id": "recent-uploads", "title": "Letzte 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 erzwingen (Routennamen werden aus dem Quellcode abgetastet, nicht aus dem Manifest), aber die Laufzeit prüft es.

Publisher-Pinning

publisher fixiert die Veröffentlichungsidentität, damit du ein Plugin nicht versehentlich unter dem falschen Konto veröffentlichen kannst.

Bei deiner ersten erfolgreichen Veröffentlichung, wenn der publisher des Manifests mit der aktiven Sitzung übereinstimmt, bleibt er wie geschrieben. Wenn du mit emdash-plugin init ein Gerüst erstellt und ihn leer gelassen hast, schreibt die CLI die DID der aktiven Sitzung zurück ins Manifest.

Das folgende Beispiel zeigt die Zeile, die die CLI schreibt, wobei der aufgelöste Handle als Kommentar zur Lesbarkeit hinzugefügt wurde:

"publisher": "did:plc:abc123def456", // jane.example.com

Bei jeder nachfolgenden Veröffentlichung 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öse es bewusst:

  • Falsche Sitzung: emdash-plugin switch <did>, dann erneut veröffentlichen.
  • Tatsächliche Übertragung des Plugins an einen neuen Publisher: Bearbeite publisher im Manifest.

Validierung ohne Veröffentlichung

emdash-plugin validate          # ./emdash-plugin.jsonc
emdash-plugin validate path/    # ein bestimmtes Verzeichnis

Offline-Schema-Prüfung mit tsc-ähnlichen datei:zeile:spalte-Diagnosen, einschließlich der Kreuzfeld-Regeln. Geeignet für einen Pre-Commit-Hook oder CI-Schritt. Doppelte Schlüssel und unbekannte Schlüssel sind Fehler (Striktmodus erkennt "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, damit die Publisher-Pin-Sicherheitsgeschichte sichtbar bleibt).

Nächste Schritte