Ir al contenido

Generación de tipos

Cada schematic mantiene un tipo Input en schema.generated.ts, derivado de su schema.json. pbuilder-codegen — un binario incluido en @pbuilder/sdk — escribe ese archivo. Cuando cambias schemas en varias colecciones, una sola ejecución los regenera a todos a partir de lo que registra project-builder.json; no hay una segunda lista de directorios que mantener sincronizada.

pbuilder-codegen es un script de Node.js, así que sirve cualquier ejecutor de paquetes: npx, pnpm exec, yarn o bunx. Los ejemplos usan bunx, como el resto de esta documentación.

Quieres regenerar… Ejecuta Qué selecciona
Todos los schematics registrados del proyecto en el que estás bunx pbuilder-codegen El project-builder.json más cercano, subiendo desde el directorio actual
Todos los schematics registrados de un proyecto concreto bunx pbuilder-codegen --project <directory> Exactamente el project-builder.json de ese directorio, desde cualquier directorio de trabajo — sin búsqueda hacia arriba
Un solo schematic bunx pbuilder-codegen <schematic-directory> Solo ese directorio

Usa los modos de proyecto después de editar varios schemas, en CI o como tu script generate:types. Usa el modo de un directorio mientras iteras sobre un schematic — es lo que enseña Tu primer schematic, y lo que builder new schematic ejecuta por ti automáticamente.

Los modos de proyecto procesan todas las colecciones, no solo default.

Un workspace registra un schematic por directorio y una segunda colección mediante un manifiesto cuya factory es un módulo compilado:

workspace/
├── package.json
├── project-builder.json
└── schematics/
├── collection.json
├── hello/
│ ├── factory.ts
│ └── schema.json
└── dist/
├── widget.js
└── schema.json
project-builder.json
{
"collections": {
"local": { "hello": { "path": "./schematics/hello" } },
"shared": { "path": "./schematics/collection.json" }
}
}
schematics/collection.json
{
"schematics": {
"widget": { "factory": "./dist/widget.js#createWidget" }
}
}
schematics/hello/schema.json
{
"properties": {
"name": { "type": "string", "label": "Service name", "required": true }
}
}
schematics/dist/schema.json
{
"properties": {
"label": { "type": "string", "label": "Widget label", "required": true }
}
}
  1. Ejecuta el codegen desde cualquier lugar dentro del workspace — aquí, desde la carpeta de un schematic:

    Terminal window
    cd schematics/hello
    bunx pbuilder-codegen
    pbuilder-codegen: generated 2, failed 0, duplicates 0

    El código de salida es 0.

  2. Revisa las salidas. Cada schema recibe un schema.generated.ts a su lado — para la entrada del manifiesto, junto al dist/widget.js compilado que nombra el puntero:

    schematics/
    ├── dist/
    │ ├── schema.generated.ts ← new
    │ ├── schema.json
    │ └── widget.js
    └── hello/
    ├── factory.ts
    ├── schema.generated.ts ← new
    └── schema.json
    schematics/dist/schema.generated.ts
    // AUTO-GENERATED by pbuilder-codegen — do not edit. Regenerate: pbuilder-codegen <package-dir>
    // @schema-digest sha256:…
    export type Input = {
    /** Widget label */
    label: string;
    };
  3. O apunta al proyecto desde fuera, por ejemplo desde la raíz de un monorepo o un script de CI:

    Terminal window
    bunx pbuilder-codegen --project ./workspace

    El resultado es el mismo.

  • Sin argumentos, la búsqueda sube desde el directorio actual, y el primer project-builder.json encontrado es el que manda. Si ese archivo está mal formado, no se puede leer, es un directorio o es un enlace roto, la ejecución falla — nunca recurre a un archivo válido más arriba.
  • Con --project <directory>, se usa exactamente ese directorio. Pasa un directorio, no la ruta al archivo de configuración.
  • Una configuración ausente o mal formada falla antes de generar nada. Una configuración sin collections, o con collections vacío, termina con éxito sin trabajo.
  • El directorio del proyecto seleccionado es el límite de escritura. Si project-builder.json es un enlace simbólico, el directorio de su destino no obtiene permiso de escritura.

Dónde está instalado el SDK no influye en la selección: el directorio de trabajo y --project deciden qué proyecto se ejecuta.

Dos formas de registro tienen archivos que generar:

Registro Ejemplo Reglas
Directorio del schematic "hello": { "path": "./schematics/hello" } El directorio debe contener exactamente un factory.ts o factory.js regular — ninguno o ambos es un error. El schema es el schema.json de ese directorio.
Manifiesto de colección "shared": { "path": "./schematics/collection.json" } Cada puntero de factory necesita un export explícito: module#export, dividido en el último #, con un módulo no vacío y un export con forma de identificador (default es válido). La ruta del módulo es relativa al manifiesto y debe ser un archivo real. El schema es el schema.json junto a ese módulo.

Ambos valores de path se resuelven contra el directorio del proyecto seleccionado; se aceptan rutas absolutas siempre que el resultado quede dentro del proyecto. Las factories nunca se importan ni se ejecutan — el codegen solo lee archivos. Se aceptan archivos JSON con una marca de orden de bytes (BOM) inicial.

No soportado:

  • Registros inline — schematics embebidos en project-builder.json, como los que crea builder new schematic --inline — no tienen archivo que escribir. Cada uno cuenta como un fallo. Un nombre registrado a la vez inline y como directorio también falla.
  • Búsqueda de paquetes, inferencia de extensiones, herencia, rutas codificadas como URL y schematics que están en disco pero no registrados. El codegen no recorre carpetas.

Toda ejecución de proyecto termina con una línea de resumen en stdout:

pbuilder-codegen: generated G, failed F, duplicates D

Las advertencias sobre entradas individuales van a stderr. El código de salida es 1 si alguna entrada falló, y 0 en caso contrario — incluida una ejecución sin trabajo.

Una ejecución continúa después de que falla una entrada. En este ejemplo, drafts/broken tiene una propiedad sin label, y legacy/broken-copy vuelve a registrar el mismo directorio:

project-builder.json
{
"collections": {
"local": { "hello": { "path": "./schematics/hello" } },
"drafts": { "broken": { "path": "./schematics/broken" } },
"legacy": { "broken-copy": { "path": "./schematics/broken" } }
}
}
$ bunx pbuilder-codegen
pbuilder-codegen: "drafts/broken" ("<workspace>/schematics/broken"): "pbuilder-codegen: <workspace>/schematics/broken/schema.json: property \"title\" is missing a label"
pbuilder-codegen: generated 1, failed 1, duplicates 1
$ echo $?
1

Cómo se cuentan las entradas:

  • Duplicados — los registros que resuelven a un destino ya intentado en esta ejecución cuentan una vez en duplicates, aunque el primer intento haya fallado; no suman otro fallo. Los alias mediante enlaces simbólicos de directorio se reconocen como el mismo destino. Los archivos distintos unidos por hard links, no.
  • Fallos — cada registro que no se puede resolver, es inválido o es rechazado cuenta por separado.
  • No es una transacción — los archivos generados con éxito quedan escritos aunque otra entrada falle. Una entrada que no pasa la validación conserva su schema.generated.ts anterior.

Corrige el schema o el registro reportado y vuelve a ejecutar. Nunca edites schema.generated.ts a mano.

Deja que el código de salida haga fallar el job. Que los archivos estén en disco no prueba que toda la ejecución haya tenido éxito:

Terminal window
bunx pbuilder-codegen

Si encadenas la salida con un pipe, conserva el código de salida — por ejemplo con set -o pipefail en Bash. Para detectar además schemas cambiados sin regenerar, busca diferencias después:

Terminal window
bunx pbuilder-codegen
git diff --exit-code -- '*schema.generated.ts'

Con sdk.root, node_modules/@pbuilder/sdk es un enlace que creó la CLI, no un paquete que instaló tu gestor de paquetes — así que no hay shim node_modules/.bin/pbuilder-codegen. Los ejecutores de paquetes fallan entonces de una de estas formas:

  • bunx pbuilder-codegen y npx pbuilder-codegen reportan que falta el binario, aunque el SDK está presente y builder execute funciona.
  • Si además hay un SDK instalado globalmente, npx puede ejecutar esa copia global sin avisarte — posiblemente una versión distinta de la que indica sdk.root.
  • Las versiones recientes de pnpm ejecutan una instalación antes de pnpm exec. Eso hace que el comando funcione, pero escribe un pnpm-lock.yaml y un shim en tu proyecto — justo el tipo de cambio que sdk.root existe para evitar.

El script generate:types que agrega builder init llama a pbuilder-codegen por nombre, así que también se ve afectado.

Ejecuta el script directamente con Node. Acepta las mismas formas — sin argumento, --project <directory> o un directorio de schematic:

Terminal window
node node_modules/@pbuilder/sdk/dist/bin/pbuilder-codegen.js
node node_modules/@pbuilder/sdk/dist/bin/pbuilder-codegen.js schematics/hello

El enlace existe una vez que corrió un builder execute con commit. Antes de eso, usa la raíz configurada: node <sdk.root>/dist/bin/pbuilder-codegen.js.

Lo mismo aplica cuando el enlace se creó con --sdk-root o BUILDER_SDK_ROOT en lugar de sdk.root. Antes de que exista el enlace, ejecuta el script desde ese valor: node $BUILDER_SDK_ROOT/dist/bin/pbuilder-codegen.js.

builder new schematic no necesita nada de esto con sdk.root: resuelve la raíz configurada por su cuenta y regenera los tipos del nuevo schematic. No lee BUILDER_SDK_ROOT: cuando la variable es tu única fuente de SDK, advierte que no encontró @pbuilder/sdk y omite schema.generated.ts — ejecuta el script como se indica arriba.

El codegen solo escribe donde la ejecución tiene permitido hacerlo:

Modo Límite de escritura
Proyecto (pbuilder-codegen, --project) El directorio del proyecto seleccionado
Un directorio (pbuilder-codegen <dir>) El ancestro más cercano del directorio de trabajo que tenga un package.json, o el propio directorio de trabajo si no hay ninguno

Tanto el directorio del schematic como el archivo de salida — una vez resueltos los enlaces simbólicos — deben quedar dentro de ese límite. Un destino fuera de él no se escribe: en una ejecución de proyecto se reporta como refusing to write outside the selected project root y cuenta como fallo. Corrige el registro o ejecuta el codegen desde el proyecto al que pertenece el schematic.

En Linux y macOS, schema.generated.ts debe no existir o ser un archivo regular. Una salida que es un enlace simbólico se rechaza — aunque su destino esté dentro del proyecto — y lo mismo ocurre con cualquier otra entrada no regular:

$ bunx pbuilder-codegen schematics/hello
pbuilder-codegen: schematics/hello/schema.generated.ts: refusing symbolic-link output

En una ejecución de proyecto, la misma entrada reporta cannot write a regular, non-symbolic-link output. builder new schematic lo muestra como new_codegen_failed. Reinstalar el SDK no ayuda — la causa es la disposición de los archivos.

Para migrar:

  1. Confirma que la entrada es un enlace y anota a dónde apunta:

    Terminal window
    ls -l schematics/hello/schema.generated.ts
  2. Elimina solo el enlace. No toques su destino:

    Terminal window
    rm schematics/hello/schema.generated.ts
  3. Regenera un archivo normal en su lugar:

    Terminal window
    bunx pbuilder-codegen schematics/hello

Los enlaces de directorio son distintos: un directorio de schematic al que se llega mediante un enlace simbólico sigue funcionando, siempre que el directorio resuelto y su salida queden dentro del límite de escritura. Crear una salida que falta y reemplazar una regular existente — incluso con contenido más corto — funciona como siempre.

  • Tu primer schematic — el flujo de un schematic de principio a fin.
  • builder new schematic — crea un schematic y ejecuta el codegen para él.
  • builder init — el script generate:types que agrega, que ejecuta el codegen una vez por cada schema.json que encuentra bajo schematics/.