Ir al contenido

Tu primer schematic

Cada paso de abajo es ejecutable exactamente como está escrito. Inicializarás un workspace, generarás el esqueleto de un schematic, declararás sus entradas tipadas, escribirás el factory y lo ejecutarás — dos veces, para ver la idempotencia en acción. Instala la CLI y Bun primero si aún no lo has hecho.

  1. Inicializa un workspace (dentro de cualquier proyecto — nuevo o existente):

    Terminal window
    builder init

    Esto crea project-builder.json (la configuración del workspace), una carpeta schematics/ y una skill de IA en .claude/skills/pbuilder/ — una referencia de comandos que tu agente de código detecta automáticamente, sin configuración adicional. También instala @pbuilder/sdk como dependencia de desarrollo, creando primero un package.json si la carpeta no tiene uno.

  2. Genera el esqueleto de un schematic:

    Terminal window
    builder new schematic hello

    Obtienes un paquete registrado y listo para editar:

    schematics/hello/
    ├── schema.json # entradas tipadas — el contrato con el usuario
    ├── schema.generated.ts # generado a partir de schema.json — nunca lo edites
    └── factory.ts # tu lógica de autoría

    project-builder.json ahora lo lista bajo collections.default.hello.

  3. Declara las entradas — reemplaza el contenido del schema.json generado con lo siguiente; toda propiedad necesita un label (el codegen falla sin él):

    schematics/hello/schema.json
    {
    "properties": {
    "name": { "type": "string", "label": "Service name", "required": true }
    }
    }

    Los tipos de propiedad son string, number, boolean y enum (que requiere un array choices no vacío); default y description son opcionales.

  4. Regenera los tipos de entrada:

    Terminal window
    bunx pbuilder-codegen schematics/hello

    Esto reescribe schema.generated.ts con un tipo Input derivado de tu schema — tu factory queda tipado contra el schema, nunca contra una forma escrita a mano.

  5. Escribe el factory:

    schematics/hello/factory.ts
    import { create, find, replaceContent } from "@pbuilder/sdk/commons";
    import type { Input } from "./schema.generated.ts";
    export default async (input: Input) => {
    // crea un archivo nuevo
    create(`src/services/${input.name}.ts`, {
    template: `export const serviceName = "${input.name}";`,
    options: {},
    });
    // edición consciente del contenido: lee el árbol y luego crea o actualiza
    const existing = await find("services.txt").read();
    if (existing === undefined) {
    create("services.txt", { template: input.name, options: {} });
    } else {
    replaceContent("services.txt", `${existing}\n${input.name}`);
    }
    };

    El engine invoca el export por defecto del módulo — ese es el factory. Este construye sus cadenas de salida en TypeScript; el lenguaje de plantillas es la alternativa declarativa.

  6. Ejecútalodefault:hello es <collection>:<schematic> tal como quedó registrado en project-builder.json; las entradas se pasan como flags de la CLI:

    Terminal window
    builder execute default:hello --name=payments
    ~ services.txt
    ~ src/services/payments.ts
    ✓ done — 2 modified

    (~ marca una ruta que la ejecución escribió.)

Ahora ejecútalo otra vez con una entrada distinta:

Terminal window
builder execute default:hello --name=orders

services.txt crece una línea en lugar de ser aplastado — ese es el bucle de relectura del paso 5 haciendo su trabajo. Volver a ejecutarlo con el mismo nombre, en cambio, se rechaza con path-collision: create es fail-closed sobre una ruta existente (pasa force: true para sobrescribir deliberadamente), y como una ejecución fallida no escribe nada, tu árbol queda exactamente como estaba.

Dos reglas que ahorran tiempo de depuración

Sección titulada «Dos reglas que ahorran tiempo de depuración»
  • Pasa siempre options: {} a create, incluso cuando la plantilla no tiene tokens. El tipo lo exige — y en sitios de llamada sin tipos (JS plano, any) omitirlo pone undefined en el lote de directivas, y la escritura se rechaza por no ser representable.
  • find().read() es una tricotomía: undefined significa que el archivo no existe, "" significa que existe y está vacío. Ramifica con === undefined — nunca con if (!content), que confunde ambos casos.
  • Pruébalo sin la CLI ni un engine en ejecución — Probar schematics.
  • Verbos de mutación — todo lo que un factory puede hacerle al árbol.
  • Plantillas — el lenguaje de plantillas declarativo dentro de create().
  • Scaffolding — replica una carpeta completa de plantillas en lugar de escribir un create() por archivo.
  • Dry-run — previsualiza los cambios planeados de un factory antes de que algo se confirme.
  • Manejo de errores — el contrato estructurado AuthoringError de una ejecución rechazada.