Este es un tema avanzado para crear tu propio software sobre el registro de plugins. Si solo quieres instalar plugins en un sitio EmDash, no necesitas nada de esto: activa el registro en tu configuración y usa el panel de administración.
La parte de descubrimiento del registro es una API pública de solo lectura. El paquete @emdash-cms/registry-client la envuelve, de modo que puedes crear un directorio de plugins, una página de búsqueda o un feed de releases fuera de EmDash. El cliente se ejecuta en cualquier lugar donde fetch esté disponible: Node, Workers, el navegador o un sitio Astro. Los sitios Astro pueden usar @emdash-cms/registry-loader para exponer los mismos datos mediante una colección de contenido en vivo.
Instalar
La subruta discovery no incluye dependencias de autenticación ni de OAuth. Instala el cliente y fíjalo a una versión exacta:
npm install @emdash-cms/[email protected]
Listar y resolver paquetes
La siguiente página de Astro lista todos los plugins de un registro:
---
import { DiscoveryClient } from "@emdash-cms/registry-client/discovery";
const discovery = new DiscoveryClient({
aggregatorUrl: "https://registry.emdashcms.com",
});
const { packages } = await discovery.searchPackages({ q: "", limit: 50 });
---
<ul>
{
packages.map((pkg) => (
<li>
<a href={`/plugins/${pkg.handle ? `@${pkg.handle}` : pkg.did}/${pkg.slug}`}>
{pkg.profile?.name ?? pkg.slug}
</a>
{pkg.latestVersion && <span>v{pkg.latestVersion}</span>}
<p>{pkg.profile?.description}</p>
</li>
))
}
</ul>
El handle verificado actual está presente cuando el agregador lo ha resuelto. Conserva la forma DID como alternativa y redirígela a la URL del handle actual cuando una respuesta posterior proporcione uno. Un DID y el slug del paquete siguen siendo la identidad estable del paquete cuando un publicador cambia de handle.
Un valor exacto de q puede ser un handle, un DID o cualquiera de las dos identidades seguida de /slug. Por ejemplo, @example.com/my-gallery selecciona un paquete y example.com devuelve los paquetes de ese publicador.
Usar el live loader de Astro
Instala @emdash-cms/registry-loader y luego registra el loader en src/live.config.ts:
import { registryLoader } from "@emdash-cms/registry-loader";
import { defineLiveCollection } from "astro:content";
export const collections = {
plugins: defineLiveCollection({ loader: registryLoader() }),
};
Usa getLiveCollection("plugins", { q, limit }) para resultados acotados y getLiveEntry("plugins", { publisher, slug }) para un solo paquete. Añade includeLatestRelease: true al filtro de la colección cuando un listado necesite datos de release como los iconos; cuesta una solicitud adicional al registro por cada paquete que tenga un release publicado, y una entrada cuyo release no se pueda cargar en 3 segundos se devuelve sin latestRelease. Pasa la pista de caché devuelta a Astro.cache.set() cuando el sitio tenga un proveedor de caché de Astro. Las colecciones en vivo de Astro no devuelven metadatos de paginación, así que usa DiscoveryClient.searchPackages() directamente cuando la interfaz necesite el siguiente cursor.
Una página de detalle de paquete obtiene un plugin por su DID y su slug, y luego obtiene el último release:
import { DiscoveryClient } from "@emdash-cms/registry-client/discovery";
const discovery = new DiscoveryClient({
aggregatorUrl: "https://registry.emdashcms.com",
});
export async function getPlugin(did: string, slug: string) {
const pkg = await discovery.getPackage({ did, slug });
const latest = await discovery.getLatestRelease({
did: pkg.did,
package: pkg.slug,
});
return { pkg, latest };
}
Métodos de descubrimiento
El cliente expone un método por cada consulta del agregador:
searchPackages({ q, capability?, limit?, cursor? })— búsqueda de texto libre, opcionalmente filtrada a paquetes que declaran una categoría de acceso determinada. Devuelve{ packages, cursor? }.resolvePackage({ handle, slug })— resuelve un paquete a partir de un handle y un slug.getPackage({ did, slug })— obtiene un paquete por su DID y su slug.listReleases({ did, package, limit?, cursor? })— releases en orden descendente de versión semántica, incluidos los releases retirados (yanked).getLatestRelease({ did, package })— el release más alto no retirado seleccionado por el agregador.
getPackage() y resolvePackage() pueden devolver historicalReleaseCount y
releaseHistoryComplete. Estos campos describen el historial operativo que conserva el agregador,
no metadatos firmados por el publicador. Trata un recuento de uno como un primer release solo cuando
releaseHistoryComplete sea true. La evidencia ausente o incompleta no debe eludir una política
de antigüedad de releases.
getPackageStatus() y resolvePackageStatus() envuelven sus consultas de paquete correspondientes y
asignan la respuesta segura ListingUnavailable a { status: "unavailable" }. Un resultado correcto tiene
{ status: "passed", value }. Usa estos métodos en una interfaz de usuario que necesite distinguir un
listado indexado pero no disponible de un paquete inexistente sin renderizar contenido de error
controlado por el publicador.
Usa el helper de retirada antes de mostrar o seleccionar un release:
import {
DiscoveryClient,
type ValidatedReleaseView,
} from "@emdash-cms/registry-client/discovery";
import { evaluateRegistryReleaseWithdrawal } from "@emdash-cms/registry-client/withdrawal";
const discovery = new DiscoveryClient({
aggregatorUrl: "https://registry.emdashcms.com",
});
export function canShowRelease(release: ValidatedReleaseView) {
const result = evaluateRegistryReleaseWithdrawal(release, discovery.labelerPolicy);
return release.release !== null && !result.withdrawn;
}
withdrawn es true cuando las etiquetas aplicables retiran el release del uso. Los datos de etiqueta no válidos
fallan de forma cerrada (fail closed): withdrawn y malformed son ambos true.
Manejar registros no confiables
El agregador es un índice no confiable que retransmite registros que no ha creado, por lo que el cliente valida cada uno en el límite. De ello se derivan dos reglas:
- Los campos
profileyreleasepueden sernull. Cuando un registro retransmitido no supera la validación, el cliente lo expone comonullen lugar de hacer fallar toda la llamada, de modo que un registro mal formado no deje en blanco una página de búsqueda. Comprueba siempre los valores nulos antes de leerpkg.profile?.nameolatest.release?.artifacts.package. - Valida tú mismo los esquemas de URL antes de renderizar. La validación comprueba la estructura, no la seguridad de las URL: un campo
uripuede llevar un esquemajavascript:. Aplica tu propia lista de permitidoshttp/httpsantes de colocar cualquier URL proporcionada por el registro en unhrefosrc.
Una respuesta que no sea 2xx lanza ClientResponseError (reexportado desde el paquete), con .error, .description, .status y .headers. El agregador de referencia devuelve solo revisiones de CID exacto aprobadas por todas las fuentes de etiquetas positivas requeridas. Una cabecera atproto-accept-labelers declara DID simples configurados para la identidad de la solicitud y de la caché. El agregador valida la declaración, pero su política configurada sigue siendo la que prevalece.
Filtrar por compatibilidad de host
Un release puede declarar requisitos de entorno (un rango de versiones de EmDash o de Astro) en su bloque requires. La subruta @emdash-cms/registry-client/env los evalúa, de modo que un directorio puede marcar los releases que no se ejecutarán en un host determinado:
import { checkEnvCompatibility, hostEnvFromVersions } from "@emdash-cms/registry-client/env";
import type { ValidatedReleaseView } from "@emdash-cms/registry-client/discovery";
const host = hostEnvFromVersions("0.37.0", "7.0.0");
// Pass a getLatestRelease() result. The returned array is empty when the
// release runs on this host.
export function envMismatches(latest: ValidatedReleaseView) {
return checkEnvCompatibility(latest.release?.requires, host);
}
Qué leer a continuación
- El registro de plugins — activa y usa el registro en un sitio EmDash
- Empaquetar y publicar — publica un plugin en el registro