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.
Fail-closed por diseño
Sección titulada «Fail-closed por diseño»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.
Cómo aparecen los errores
Sección titulada «Cómo aparecen los errores»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í.
Cuando una factory lanza un Error común
Sección titulada «Cuando una factory lanza un Error común»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.
La reescritura es best-effort y solo cubre formas de ruta. Cualquier otra cosa que interpoles en un mensaje, como el valor de una variable de entorno o un token, se imprime tal cual. Mantén los secretos fuera de los mensajes de error.
verb: el verbo de cara al autor cuya llamada fue rechazada:"create","modify","remove","rename","move","copy"o"copyIn".undefinedpara 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.undefinedcuandoverbesundefined.reason: la causa cerrada del rechazo (ver abajo).origin: derivado dereason:"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.
La peculiaridad de la etiqueta "modify". "modify" etiqueta la mutación de wire
subyacente, y la comparten tanto las llamadas a .replaceContent() (el reemplazo total de
commons/dialectos) como el escape de AST .modify(fn) de un handle de dialecto. Las dos
llamadas se traducen a la misma directiva de wire, así que un rechazo en cualquiera de las dos
aparece como verb: "modify". Es deliberado, no un nombre obsoleto que sobrevivió a un
renombrado. Escribe tus aserciones contra "modify", nunca contra "replaceContent".
Valores de reason
Sección titulada «Valores de reason»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. |
source-outside-package se eliminó de @pbuilder/sdk. El SDK ya no re-deriva un techo
de contención para las fuentes locales del paquete. Migración: elimina el brazo
case "source-outside-package": de cualquier switch (err.reason) exhaustivo. TypeScript te
lo va a señalar.
Capturar y recuperarse
Sección titulada «Capturar y recuperarse»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;}Escribir buenos caminos de fallo
Sección titulada «Escribir buenos caminos de fallo»Escribe factories que traten el rechazo como un resultado esperado:
- Previene
path-collisionleyendo 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 defind().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: truesolo 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
switchexhaustivo sobrereason. 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 desource-outside-packagede arriba. - En tests, escribe aserciones sobre los campos estructurados.
result.error.verb,.pathy.reasonfijan cuál directiva falló y por qué, lo que las hace mucho más resistentes a mutaciones que hacer matching sobre el texto demessage. Recuerda la peculiaridad de la etiqueta"modify"cuando la llamada fallida es unreplaceContent.
Próximos pasos
Sección titulada «Próximos pasos»- Verbos de mutación: los siete verbos de autoría y la regla de la tricotomía de lectura.
- Directivas pendientes: previsualiza los cambios planificados de un factory antes de que algo se confirme.