Il tuo primo plugin sandboxed

In questa pagina

Questo tutorial crea un plugin sandboxed che registra gli eventi di salvataggio dei contenuti ed espone una piccola route di health. Scaffoldi il pacchetto con la CLI del plugin, aggiungi un hook e una route, registri il plugin con un sito EmDash e confermi che entrambi gli handler vengono eseguiti.

Se non hai ancora scelto un formato di plugin, leggi prima Scegliere un formato di plugin.

Prerequisiti

Ti servono:

Scaffoldare il plugin

  1. Esegui lo scaffolder del plugin dalla directory che conterrà il nuovo progetto.

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

    Il comando chiede publisher, autore, contatto di sicurezza e repository sorgente, poi mostra un riepilogo del progetto prima di creare questa struttura:

    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. Installa le dipendenze del pacchetto generato.

    cd save-log
    pnpm install

Definire l’accesso e lo storage del plugin

emdash-plugin.jsonc contiene l’identità del plugin, le informazioni del registry e il contratto di fiducia. Aggiungi la capability content:read perché content:afterSave espone il contenuto salvato al plugin. Dichiarare una collection di storage events così che l’hook possa mantenere un record interrogabile di ogni salvataggio.

Il manifesto seguente contiene i campi usati in questo tutorial. Mantieni i valori di publisher, author e security prodotti dallo scaffolder.

{
	"$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 dichiarazione content:read dice all’operatore del sito che l’hook riceve contenuto salvato ed è richiesta quando questo formato di plugin viene eseguito in-process. Accedere a ctx.storage.events lancerebbe un’eccezione se la collection fosse assente. Il riferimento al manifesto spiega i restanti campi e le regole di convalida.

Aggiungere l’hook e la route

Sostituisci il src/plugin.ts generato con la seguente definizione runtime:

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 assegna la definizione a una costante tipizzata SandboxedPlugin e la esporta come default. L’annotazione dà all’hook e alla route i tipi dei parametri senza aggiungere il runtime EmDash al bundle né produrre percorsi di dichiarazione specifici del package manager.

Gli handler degli hook ricevono (event, ctx). Gli handler delle route ricevono (routeCtx, ctx). La route health è pubblica e in sola lettura, quindi può essere verificata senza una sessione di amministrazione. Le route pubbliche sono esposte a Internet; Route API spiega l’autenticazione e le regole di origine del browser prima di esporre dati reali o mutazioni.

Aggiornare il test generato

Il test scaffolded invoca la route hello originale tramite l’host di trasporto Worker Loader. Sostituiscilo con un test per 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" });
	});
});

Il test costruisce il plugin e invoca la route tramite Worker Loader e PluginBridge. La guida al testing dei plugin sandboxed copre hook, fixture di contenuto, asserzioni di storage e i limiti dei test workerd locali.

Validare e compilare

Esegui il test generato, valida il manifesto e costruisci gli artefatti npm.

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

La build crea:

  • dist/plugin.mjs, con il codice dell’hook e della route;
  • dist/manifest.json, con il manifesto runtime e i nomi di hook e route scoperti; e
  • dist/index.mjs, il descriptor esportato di default che un sito importa.

dist/ è output generato. Lo scaffold lo esclude da Git perché la build del plugin lo ricrea.

Registrare il plugin

Installa il pacchetto locale nel tuo sito EmDash. Esegui questo comando dalla directory del sito e regola il percorso relativo se i progetti non sono fratelli.

pnpm add file:../save-log

Importa l’export di default generato in astro.config.mjs e aggiungilo a 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",
		}),
	],
});

Questo esempio usa il runner workerd di Node.js. Mantieni il runner già configurato dal tuo sito se usa Cloudflare Workers o un’altra configurazione supportata.

Eseguire il plugin

Avvia entrambi i processi di sviluppo:

  1. Esegui pnpm dev nella directory del plugin. La CLI ricostruisce il plugin quando cambiano sorgente o manifesto.
  2. Esegui il comando di sviluppo del sito nella directory del sito.

Apri la seguente route sul sito:

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

La risposta contiene l’envelope API standard e il valore restituito dal plugin:

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

Salva una voce nell’admin EmDash. Il log del sito contiene Content save recorded e l’hook scrive un elemento nella collection events del plugin.

Continuare a costruire

  • Hooks spiega eventi degli hook, capability, ordine ed errori.
  • Route API copre convalida, permessi, route pubbliche ed esposizione MCP.
  • Block Kit aggiunge una pagina di amministrazione senza spedire JavaScript del browser.
  • Impostazioni memorizza la configurazione del plugin specifica del sito.
  • Storage copre query indicizzate e paginazione.
  • Testing copre test di trasporto sandbox diretti e test di azioni host supportati dal runtime.
  • Bundling e pubblicazione pubblica il plugin nel registry.