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. Este ejemplo lo ejecuta 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, eso significa 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; no tener ninguno o tener ambos es un error. El schema es el schema.json de ese directorio.
Definición 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 a la definición de colección 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, incluso en 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, que es 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

Una vez resueltos los enlaces simbólicos, tanto el directorio del schematic como el archivo de salida 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, porque 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/.