Ir al contenido

Manejo de errores

Un verbo de autoría que resulta rechazado, ya sea por el engine o por el propio SDK antes de cualquier ida y vuelta, lanza AuthoringError. Lleva todo lo que necesitas para recuperarte, en vocabulario de autor.

Un run fallido no escribe nada. Los verbos solo agendan directivas contra el árbol de staging del engine; las escrituras a disco ocurren en el Commit, después de que tu factory retorna. Por eso, un AuthoringError lanzado (o cualquier error) antes del retorno deja el proyecto de destino exactamente como estaba. No hay estado de escritura parcial que limpiar, y por eso los verbos mismos son estrictos: create rechaza una ruta existente, replaceContent rechaza una faltante, y las sobrescrituras requieren un { force: true } explícito. Consulta Verbos de mutación para la semántica exacta de cada verbo.

Llamar al runner directamente lanza; runFactoryForTest captura el mismo AuthoringError en result.error:

import { runFactoryForTest } from "@pbuilder/sdk/testing";
import { AuthoringError } from "@pbuilder/sdk/commons";
const result = await runFactoryForTest(run, input);
if (result.error instanceof AuthoringError) {
switch (result.error.reason) {
case "path-collision":
console.error(`${result.error.verb} collided at ${result.error.path}`);
break;
default:
console.error(result.error.message);
}
}

La guía de testing cubre el harness en sí.

No todo fallo es un AuthoringError. Cuando tu factory lanza un Error común (throw new Error(...), o un error de una librería que llama), el runner imprime su mensaje en stderr como pbuilder-runner: <mensaje>, cortado a los 2.000 caracteres. Antes de imprimirlo, reescribe las rutas absolutas del mensaje para que no revelen dónde está cada cosa en tu máquina:

Ruta en tu mensaje Se muestra como
Dentro de la raíz del proyecto <project-root>/..., por ejemplo open '<project-root>/src/a.ts'
Dentro del paquete @pbuilder/sdk <sdk>/...
Una ruta de Windows, UNC o WSL <outside-project>
Cualquier otra ruta absoluta POSIX una ruta relativa con ../ desde la raíz del proyecto
Una URL queda tal cual, salvo que una URL file:// conserva file:// y su ruta se reescribe

Así, una factory que falla con

ENOENT: no such file or directory, open '/home/ana/acme/src/a.ts'

se ve como

pbuilder-runner: ENOENT: no such file or directory, open '<project-root>/src/a.ts'

El runner inserta <project-root> y <sdk> a propósito. Ver uno en un error no significa que tu factory o una plantilla estén rotas.

  • verb: el verbo de cara al autor cuya llamada fue rechazada: "create", "modify", "remove", "rename", "move", "copy" o "copyIn". undefined para rechazos a nivel de batch que no tienen una única llamada infractora.
  • path: la ruta del lado origen, declarada por el autor, de la llamada fallida. undefined cuando verb es undefined.
  • reason: la causa cerrada del rechazo (ver abajo).
  • origin: derivado de reason: "write-rejected" (el engine rechazó una escritura) o "authoring-rejected" (el SDK atrapó un mal uso antes de cualquier ida y vuelta al engine).
  • appliedCount: cuántas directivas se aplicaron dentro del run fallido antes del infractor. Es solo un diagnóstico. Un run rechazado descarta todo, así que este número nunca promete persistencia parcial.

reason es una unión cerrada. Se espera que los bloques switch sean exhaustivos, y obtienen un error de compilación si falta un valor:

reason Significado
path-collision La ruta de destino ya existe y no se pasó { force: true }.
path-not-found La ruta de destino no existe.
unrepresentable-content El contenido no pudo representarse en el formato del engine.
changes-too-large El tamaño total de cambios del run excede el tope del engine.
outside-session Se llamó a un verbo de autoría fuera de una sesión activa.
unknown El rechazo no pudo clasificarse.
invalid-input El SDK rechazó los argumentos de una llamada antes de cualquier ida y vuelta al engine.
reserved-name La llamada usó un nombre reservado por el SDK o el engine.
source-not-found Una fuente local del paquete (scaffold/copyIn/create({ templateFile })) no existe.
source-not-regular-file Una fuente local del paquete no es un archivo regular.
source-unreadable Una fuente local del paquete existe pero no pudo leerse.

Es la misma forma que arriba, ahora exhaustiva, con un case para cada valor de reason:

switch (err.reason) {
case "path-collision":
console.error(`${err.verb} collided at ${err.path}`);
break;
case "path-not-found":
case "unrepresentable-content":
case "changes-too-large":
case "outside-session":
case "unknown":
case "invalid-input":
case "reserved-name":
case "source-not-found":
case "source-not-regular-file":
case "source-unreadable":
console.error(err.message);
break;
}

Escribe factories que traten el rechazo como un resultado esperado:

  • Previene path-collision leyendo primero. El rechazo más común es una re-ejecución que choca con un archivo creado por el run anterior. Ramifica sobre la tricotomía de find().read() (crear cuando está ausente, actualizar cuando está presente) en lugar de recurrir a { force: true }, que convierte cada re-ejecución en una sobrescritura silenciosa.
  • Pasa force: true solo cuando sobrescribir es la intención. Las colisiones fail-closed son el SDK protegiendo la salida anterior; forzarlas elimina esa protección para esta ejecución y todas las futuras.
  • Deja que los estados inesperados fallen ruidosamente. Un run rechazado descarta todo y deja el árbol intacto, así que lanzar siempre es seguro. Un factory que detecta un estado que no entiende debería lanzar en lugar de adivinar.
  • Haz switch exhaustivo sobre reason. La unión cerrada implica que el compilador te avisa cuando una nueva versión del SDK agrega (o elimina) una causa de rechazo, como muestra la eliminación de source-outside-package de arriba.
  • En tests, escribe aserciones sobre los campos estructurados. result.error.verb, .path y .reason fijan cuál directiva falló y por qué, lo que las hace mucho más resistentes a mutaciones que hacer matching sobre el texto de message. Recuerda la peculiaridad de la etiqueta "modify" cuando la llamada fallida es un replaceContent.