Saltar al contenido

Empaquetar, publicar y pulir una integración

El último tramo: convertir una integración que funciona en tu proyecto en un paquete que otros instalan. El package.json con la keyword astro-integration y astro como peerDependency, las opciones tipadas y validadas con zod, aportar variables al esquema de astro:env desde la integración, exponer tipos con injectTypes, probarla con proyectos de prueba y build programático, y el decálogo de buenas prácticas que separan una integración amable de una hostil.

El package.json que Astro reconoce

Empaquetar una integración es sobre todo declarar bien su package.json. Tres detalles la convierten en ciudadana del ecosistema: la keyword astro-integration, que hace que astro add y los buscadores la encuentren; astro declarado como peerDependencies en lugar de dependencia normal, para compartir la instancia del usuario y no duplicarla; y un mapa exports que exponga el punto de entrada y los ficheros que inyectas.

JSON
{  "name": "astro-mi-integracion",  "type": "module",  "keywords": ["astro-integration"],  "exports": {    ".": "./dist/index.js",    "./middleware.js": "./dist/middleware.js"  },  "peerDependencies": {    "astro": "^7.0.0"  }}

Fíjate en que los entrypoint que pasaste a injectRoute o addMiddleware deben ser especificadores importables, y por eso tienen que figurar en exports: si un fichero no está exportado, el proyecto que instale tu paquete no podrá resolverlo. El exports no es burocracia —es el contrato de qué partes de tu paquete son públicas—.

Opciones tipadas y astro:env

La fábrica recibe las opciones del usuario, y una integración robusta no confía en ellas a ciegas: las valida. La herramienta idiomática es zod, el mismo validador que usan los esquemas de contenido, que comprueba la forma en runtime y deriva el tipo para el editor de una sola definición.

TypeScript
import { z } from 'astro/zod';const esquema = z.object({  saludo: z.string().default('hola'),  intensidad: z.number().min(1).max(10).default(3),});export default function miIntegracion(opciones: unknown) {  const opts = esquema.parse(opciones);  return { name: 'astro-mi-integracion', hooks: { /* ... */ } };}

Cuando tu integración necesita variables de entorno tipadas —una clave de API, una URL de servicio—, no las leas de process.env a mano: aporta su definición al esquema de astro:env desde astro:config:setup, fundiéndola con updateConfig. Así el usuario obtiene las mismas garantías de validación y tipado que para sus propias variables, y tú las consumes desde astro:env/server.

TypeScript
import { envField } from 'astro/config';'astro:config:setup': ({ updateConfig }) => {  updateConfig({    env: {      schema: {        MI_API_KEY: envField.string({ context: 'server', access: 'secret' }),      },    },  });},

Publicar los tipos de tu API

Una integración en TypeScript debe viajar con sus tipos, y hay dos superficies que tipar. La primera es la API pública: las opciones de la fábrica y su valor de retorno, que se compilan a un .d.ts junto al código y se anuncian en package.json. La segunda son los ambientes que tu integración añade al proyecto del usuario —módulos virtuales, locals que rellena tu middleware— que se declaran con injectTypes en astro:config:done.

TypeScript
'astro:config:done': ({ injectTypes }) => {  injectTypes({    filename: 'tipos.d.ts',    content: `declare namespace App {  interface Locals {    miIntegracion: { saludo: string };  }}`,  });},

Con esto, cuando el usuario lea Astro.locals.miIntegracion el editor conocerá su forma sin que él declare nada. La regla es simple: todo lo que tu integración inyecta en el espacio del usuario —un import virtual, una propiedad de locals, un global— merece su declaración, o se sentirá como magia sin tipo.

Probar y convivir: buenas prácticas

Se prueba una integración de dos maneras complementarias. En pequeño, llamando a la fábrica y comprobando el objeto que devuelve, o invocando un hook con utilidades falsas y verificando que llama a updateConfig o injectRoute como esperas. En grande, con un proyecto de prueba —una mini app que enrola la integración— sobre el que corres el build de Astro de forma programática y afirmas sobre lo que aparece en dist.

TypeScript
import { build } from 'astro';// en un test: construye un proyecto de prueba que usa la integracionawait build({ root: new URL('./fixtures/basico', import.meta.url) });// luego afirma sobre los ficheros y rutas generados en dist