Votre premier plugin sandboxed

Sur cette page

Ce tutoriel crée un plugin sandboxed qui enregistre les événements de sauvegarde de contenu et expose une petite route de santé. Vous allez générer le paquet avec la CLI du plugin, ajouter un hook et une route, enregistrer le plugin auprès d’un site EmDash et confirmer que les deux handlers s’exécutent.

Si vous n’avez pas encore choisi un format de plugin, lisez d’abord Choisir un format de plugin.

Prérequis

Vous avez besoin de :

Générer le plugin

  1. Exécutez le générateur de plugin depuis le répertoire qui contiendra le nouveau projet.

    pnpm dlx @emdash-cms/plugin-cli init save-log

    La commande demande le publisher, l’auteur, le contact de sécurité et le dépôt source, puis affiche un résumé du projet avant de créer cette structure :

    save-log/
    ├── .agents/
    │   └── skills -> ../skills
    ├── .claude/
    │   ├── CLAUDE.md -> ../AGENTS.md
    │   └── skills -> ../skills
    ├── AGENTS.md
    ├── emdash-plugin.jsonc
    ├── package.json
    ├── pnpm-workspace.yaml
    ├── README.md
    ├── skills/
    │   └── creating-plugins/SKILL.md
    ├── src/
    │   └── plugin.ts
    ├── tests/
    │   └── plugin.test.ts
    ├── tsconfig.json
    ├── vitest.config.ts
    └── .gitignore
  2. Installez les dépendances du paquet généré.

    cd save-log
    pnpm install

Définir l’accès et le storage du plugin

emdash-plugin.jsonc contient l’identité du plugin, les informations du registre et le contrat de confiance. Ajoutez la capability content:read car content:afterSave expose le contenu sauvegardé au plugin. Déclarez une collection de storage events pour que le hook puisse conserver un enregistrement interrogeable de chaque sauvegarde.

Le manifeste suivant contient les champs utilisés dans ce tutoriel. Conservez les valeurs publisher, author et security produites par le générateur.

{
	"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",

	"slug": "save-log",
	"publisher": "did:plc:abc123def456",

	"license": "MIT",
	"author": { "name": "Jane Doe", "url": "https://example.com" },
	"security": { "email": "security@example.com" },
	"description": "Records content-save events.",

	"capabilities": ["content:read"],
	"allowedHosts": [],
	"storage": {
		"events": { "indexes": ["savedAt"] },
	},
}

La déclaration content:read indique à l’opérateur du site que le hook reçoit le contenu sauvegardé et est requise lorsque ce format de plugin s’exécute in-process. Accéder à ctx.storage.events lèverait une exception si la collection était absente. La référence du manifeste explique les autres champs et les règles de validation.

Ajouter le hook et la route

Remplacez le src/plugin.ts généré par la définition runtime suivante :

import type { SandboxedPlugin } from "emdash/plugin";

const plugin: SandboxedPlugin = {
	hooks: {
		"content:afterSave": {
			handler: async (event, ctx) => {
				const savedAt = new Date().toISOString();
				const contentId = String(event.content.id);
				await ctx.storage.events.put(`${savedAt}:${contentId}`, {
					savedAt,
					collection: event.collection,
					contentId,
				});

				ctx.log.info("Content save recorded", {
					collection: event.collection,
					contentId,
				});
			},
		},
	},

	routes: {
		health: {
			public: true,
			handler: async (_routeCtx, ctx) => {
				return { ok: true, plugin: ctx.plugin.id };
			},
		},
	},
};

export default plugin;

src/plugin.ts assigne la définition à une constante typée SandboxedPlugin et l’exporte par défaut. L’annotation donne au hook et à la route leurs types de paramètres sans ajouter le runtime EmDash au bundle ni produire de chemins de déclaration spécifiques au gestionnaire de paquets.

Les handlers de hooks reçoivent (event, ctx). Les handlers de routes reçoivent (routeCtx, ctx). La route health est publique et en lecture seule, elle peut donc être vérifiée sans session d’administration. Les routes publiques sont exposées à Internet ; Routes API explique l’authentification et les règles d’origine du navigateur avant d’exposer de vraies données ou mutations.

Mettre à jour le test généré

Le test généré invoque la route hello d’origine via l’hôte de transport Worker Loader. Remplacez-le par un test de la route health :

import { afterEach, describe, expect, it } from "vitest";

import { createPluginTestHost, type PluginTestHost } from "@emdash-cms/plugin-test";

let host: PluginTestHost | undefined;

afterEach(async () => {
	await host?.dispose();
	host = undefined;
});

describe("health route", () => {
	it("identifies the running plugin", async () => {
		host = await createPluginTestHost();
		const result = await host.invokeRoute("health");
		expect(result).toEqual({ ok: true, plugin: "save-log" });
	});
});

Le test construit le plugin et invoque la route via Worker Loader et PluginBridge. Le guide de test des plugins sandboxed couvre les hooks, les fixtures de contenu, les assertions de storage et les limites des tests workerd locaux.

Valider et construire

Exécutez le test généré, validez le manifeste et construisez les artefacts npm.

pnpm run validate
pnpm run typecheck
pnpm run test
pnpm run build

La construction crée :

  • dist/plugin.mjs, contenant le code du hook et de la route ;
  • dist/manifest.json, contenant le manifeste runtime et les noms de hooks et routes découverts ; et
  • dist/index.mjs, le descripteur exporté par défaut qu’un site importe.

dist/ est une sortie générée. Le générateur l’exclut de Git car la construction du plugin le recrée.

Enregistrer le plugin

Installez le paquet local dans votre site EmDash. Exécutez cette commande depuis le répertoire du site et ajustez le chemin relatif si les projets ne sont pas frères.

pnpm add file:../save-log

Importez l’export par défaut généré dans astro.config.mjs et ajoutez-le à sandboxed :

import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import saveLog from "save-log";

export default defineConfig({
	integrations: [
		emdash({
			sandboxed: [saveLog],
			sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
		}),
	],
});

Cet exemple utilise le runner workerd de Node.js. Conservez le runner déjà configuré par votre site s’il utilise Cloudflare Workers ou une autre configuration prise en charge.

Exécuter le plugin

Démarrez les deux processus de développement :

  1. Exécutez pnpm dev dans le répertoire du plugin. La CLI reconstruit le plugin lorsque sa source ou son manifeste change.
  2. Exécutez la commande de développement du site dans le répertoire du site.

Ouvrez la route suivante sur le site :

http://localhost:4321/_emdash/api/plugins/save-log/health

La réponse contient l’enveloppe API standard et la valeur renvoyée par le plugin :

{
	"success": true,
	"data": { "ok": true, "plugin": "save-log" },
}

Enregistrez une entrée dans l’admin EmDash. Le journal du site contient Content save recorded, et le hook écrit un élément dans la collection events du plugin.

Continuer à construire

  • Hooks explique les événements de hooks, capabilities, ordre et erreurs.
  • Routes API couvre la validation, les permissions, les routes publiques et l’exposition MCP.
  • Block Kit ajoute une page d’administration sans livrer de JavaScript navigateur.
  • Paramètres stocke la configuration du plugin spécifique au site.
  • Storage couvre les requêtes indexées et la pagination.
  • Testing couvre les tests de transport sandbox directs et les tests d’actions hôte adossés au runtime.
  • Empaqueter et publier publie le plugin dans le registre.