El manifiesto del plugin

En esta página

Cada plugin sandboxed tiene un emdash-plugin.jsonc junto a su package.json. Se edita manualmente y contiene la identidad del plugin, su contrato de confianza (capacidades, hosts, almacenamiento) y los campos de perfil que muestra el registro. emdash-plugin init genera uno; la CLI lee ./emdash-plugin.jsonc automáticamente para build, dev, validate, bundle y publish.

El archivo es JSONC: se permiten comentarios y comas finales.

El siguiente ejemplo muestra un manifiesto completo para un plugin de galería de imágenes:

{
	"$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]" },

	// Perfil opcional
	"name": "Gallery",
	"description": "Bloque de galería de imágenes para EmDash.",
	"keywords": ["gallery", "images"],
	"repo": "https://github.com/example/plugin-gallery",

	// Contrato de confianza
	"capabilities": ["content:read"],
	"allowedHosts": [],
	"storage": {}
}

Identidad

CampoRequeridoNotas
slugID seguro para URL dentro del espacio de nombres del publicador. /^[a-z][a-z0-9_-]*$/, máx. 64 caracteres.
publisherLa DID o handle de tu cuenta Atmosphere. Ver Fijación de publicador.
versionNoSemver 2.0 sin metadatos de build. Normalmente omítelo — ver abajo.

slug y publisher juntos son la identidad del paquete. EmDash deriva automáticamente el identificador completo del paquete a partir de ellos.

version va en package.json

El build reconcilia la version del manifiesto con package.json#version:

  • Ambos establecidos e iguales → bien.
  • Ambos establecidos y diferentes → error grave.
  • Uno establecido → ese valor gana.
  • Ninguno establecido → error grave.

El patrón recomendado para un plugin distribuido por npm es omitir version del manifiesto y dejar que package.json sea la única fuente de verdad (tu tooling de release ya lo incrementa allí). Los plugins solo de registro sin package.json deben establecer version en el manifiesto — no hay otro lugar donde ponerlo.

Perfil

Estos alimentan el listado del registro. license, un autor (author o authors) y un contacto de seguridad (security o securityContacts) son requeridos; el resto son opcionales.

CampoRequeridoNotas
licenseExpresión SPDX ("MIT", "Apache-2.0", "MIT OR Apache-2.0"). Se usa en la primera publicación; el perfil existente gana en publicaciones posteriores.
author / authorsUno de los dos. author: { name, url?, email? } para un solo autor; authors: [...] (≤ 32) para varios. Establecer ambos es un error.
security / securityContactsUno de los dos. Cada contacto necesita al menos email o url. securityContacts: [...] (≤ 8) para varios. Establecer ambos es un error.
nameNoNombre para mostrar. Por defecto es el slug.
descriptionNoMantenlo corto (alrededor de 140 caracteres). Los valores largos pueden truncarse en listas.
keywordsNo≤ 5 entradas.
repoNoURL https:// del repositorio fuente.

Usa la forma singular author / security a menos que genuinamente tengas múltiples — es el caso común y el scaffold lo genera así.

Contrato de confianza

El contrato de confianza es capabilities, allowedHosts y storage. Los tres son vacíos por defecto, así que un plugin que no necesita privilegios adicionales puede omitirlos por completo.

{
	"capabilities": ["network:request", "content:read"],
	"allowedHosts": ["api.example.com", "*.cdn.example.com"],
	"storage": {
		"events": { "indexes": ["timestamp"] },
		"submissions": { "indexes": ["email"], "uniqueIndexes": ["token"] }
	}
}

Capacidades

Los nombres reconocidos:

CapacidadOtorga
content:read / content:writeLeer / modificar contenido del sitio vía ctx.
taxonomies:readLeer definiciones de taxonomías y términos (solo lectura).
media:read / media:writeLeer / escribir medios.
users:readLeer registros de usuarios.
email:sendEnviar email vía ctx.
network:requestHTTP saliente vía ctx.http, restringido a allowedHosts.
network:request:unrestrictedHTTP saliente a cualquier host. Se usa en lugar de network:request.
hooks.email-transport:registerRegistrar un hook de transporte de email.
hooks.email-events:registerRegistrar hooks de ciclo de vida de email.
hooks.page-fragments:registerRegistrar un hook page:fragments (solo nativo).

Dos reglas entre campos que la CLI aplica (la verificación JSON-Schema del editor no lo hace — ejecuta emdash-plugin validate):

  • network:request requiere un allowedHosts no vacío. Si el plugin realmente debe alcanzar cualquier host, usa network:request:unrestricted en su lugar.
  • network:request:unrestricted requiere que allowedHosts esté vacío — la capacidad sin restricciones ya otorga todos los hosts, así que una lista sería contradictoria.

Los patrones de host son nombres de host desnudos (sin esquema, ruta ni espacios en blanco). Un *. inicial permite subdominios: *.cdn.example.com.

Almacenamiento

Un mapa de nombre de colección → configuración de índice. Los nombres de colección siguen la misma regla /^[a-z][a-z0-9_]*$/ (el runtime usa el nombre como sufijo de tabla SQL). Los índices son nombres de campo o arrays compuestos; uniqueIndexes también son consultables — no los listes adicionalmente en indexes.

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

Superficie de administración

Opcional. Los plugins sandboxed renderizan páginas de administración y widgets de dashboard a través de Block Kit; el manifiesto solo declara dónde aparecen. Omite completamente la clave admin si el plugin no tiene UI de administración.

"admin": {
	"pages": [{ "path": "/gallery", "label": "Galería", "icon": "image" }],
	"widgets": [{ "id": "recent-uploads", "title": "Subidas recientes", "size": "half" }]
}

Un plugin que declara admin.pages o admin.widgets también debe servir una ruta admin en src/plugin.ts que renderice el contenido Block Kit — el schema no puede forzar eso (los nombres de ruta se prueban desde el código fuente, no desde el manifiesto), pero el runtime lo verifica.

Fijación de publicador

publisher fija la identidad de publicación para que no puedas publicar accidentalmente un plugin bajo la cuenta incorrecta.

En tu primera publicación exitosa, si el publisher del manifiesto coincide con la sesión activa, se queda como está escrito. Si creaste el scaffold con emdash-plugin init y lo dejaste en blanco, la CLI escribe la DID de la sesión activa de vuelta al manifiesto.

El siguiente ejemplo muestra la línea que la CLI escribe, con el handle resuelto añadido como comentario para legibilidad:

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

En cada publicación subsiguiente, la CLI resuelve la sesión activa y el publisher fijado a DIDs y los compara. Una discrepancia falla inmediatamente con MANIFEST_PUBLISHER_MISMATCH — no hay flag de override. Resuélvelo deliberadamente:

  • Sesión incorrecta: emdash-plugin switch <did>, luego publica de nuevo.
  • Transferencia genuina del plugin a un nuevo publicador: edita publisher en el manifiesto.

Validar sin publicar

emdash-plugin validate          # ./emdash-plugin.jsonc
emdash-plugin validate path/    # un directorio específico

Verificación de schema offline con diagnósticos estilo tsc archivo:línea:columna, incluyendo las reglas entre campos. Adecuado para un hook de pre-commit o paso de CI. Las claves duplicadas y las claves desconocidas son errores (el modo estricto detecta errores tipográficos como "licens").

Los flags de CLI siempre ganan

Los flags explícitos (--license, --author-name, …) sobrescriben los valores del manifiesto cuando ambos están establecidos — útil para overrides de CI. --no-manifest omite el manifiesto por completo (y advierte si existe uno en la ruta predeterminada, para que la historia de seguridad del publisher-pin permanezca visible).

Siguiente