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.
-
Inicializa un workspace (dentro de cualquier proyecto — nuevo o existente):
Terminal window builder initEsto crea
project-builder.json(la configuración del workspace), una carpetaschematics/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/sdkcomo dependencia de desarrollo, creando primero unpackage.jsonsi la carpeta no tiene uno. -
Genera el esqueleto de un schematic:
Terminal window builder new schematic helloObtienes 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íaproject-builder.jsonahora lo lista bajocollections.default.hello. -
Declara las entradas — reemplaza el contenido del
schema.jsongenerado con lo siguiente; toda propiedad necesita unlabel(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,booleanyenum(que requiere un arraychoicesno vacío);defaultydescriptionson opcionales. -
Regenera los tipos de entrada:
Terminal window bunx pbuilder-codegen schematics/helloEsto reescribe
schema.generated.tscon un tipoInputderivado de tu schema — tu factory queda tipado contra el schema, nunca contra una forma escrita a mano. -
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 nuevocreate(`src/services/${input.name}.ts`, {template: `export const serviceName = "${input.name}";`,options: {},});// edición consciente del contenido: lee el árbol y luego crea o actualizaconst 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.
-
Ejecútalo —
default:helloes<collection>:<schematic>tal como quedó registrado enproject-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ó.)
Ejecútalo de nuevo
Sección titulada «Ejecútalo de nuevo»Ahora ejecútalo otra vez con una entrada distinta:
builder execute default:hello --name=ordersservices.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.
Haz que las mutaciones sean idempotentes. Los factories se vuelven a ejecutar contra proyectos ya generados — comprueba si tu marcador, import o entrada ya está presente antes de insertarlo. El factory de arriba muestra la mecánica de relectura; un factory de producción también omitiría el append cuando el nombre ya está en la lista.
Si tu editor marca el import con extensión .ts o bun:test, solo son ajustes de
tsconfig que faltan — consulta Probar schematics; Bun por
sí solo corre bien sin ellos.
Dos reglas que ahorran tiempo de depuración
Sección titulada «Dos reglas que ahorran tiempo de depuración»- Pasa siempre
options: {}acreate, incluso cuando la plantilla no tiene tokens. El tipo lo exige — y en sitios de llamada sin tipos (JS plano,any) omitirlo poneundefineden el lote de directivas, y la escritura se rechaza por no ser representable. find().read()es una tricotomía:undefinedsignifica que el archivo no existe,""significa que existe y está vacío. Ramifica con=== undefined— nunca conif (!content), que confunde ambos casos.
Próximos pasos
Sección titulada «Próximos pasos»- 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
AuthoringErrorde una ejecución rechazada.