Package authoring
A package extends Package — the base class for reusable, importable hardware
modules. Its build() creates the parts and wires them once; consumers get the whole
sub-circuit from one constructor call. Consuming packages is covered in Packages.
typecad-pcb add package
Scaffolds a package in ./src: package.json, the package class, and (for component packages) a generated component file. Run it from the hw/ directory.
Example first
cd hw
typecad-pcb add package --component=true --kicad=true \
--symbol=MCU_Microchip_ATtiny:ATtiny3227-M \
--footprint=Package_SO:SOIC-20W_7.5x12.8mm_P1.27mm \
--name=my-tiny-board| Flag | Meaning |
|---|---|
| --empty=true | --component=true | Exactly one — an empty utility package or one wrapping a component |
| --name=<name> | Package and class name |
| --kicad=true | --local=true | --jlcpcb=true | Where the component comes from (component packages) |
| --symbol= / --footprint= | KiCAD library paths for --kicad |
| --c=<C-number> | JLCPCB part number for --jlcpcb |
| --folder=<path> | Output directory (default ./src) |
What's generated
| Path | What it is |
|---|---|
| package.json | npm metadata — publish like any other package |
| src/index.ts | The package class — what consumers import |
| src/<component>.ts | The generated component file (component packages) |
| ./build/ | Symbols, footprints, and 3D models the package ships |
Package
The abstract base class. Extend it with your own options as the generic parameter; build() does the work. Construction handles everything else: offset positioning from x/y, part collection, grouping, and the build/lib sync.
Example first
import { Package, Power } from '@typecad/pcb';
import { ATtiny3227_M } from './ATtiny3227_M';
export interface TinyBoardOptions {
vin?: Power; // caller's supply — the package wires itself to it
}
export class TinyBoard extends Package<TinyBoardOptions> {
declare u1: ATtiny3227_M;
declare c1: Component;
declare power: Power;
build(options: TinyBoardOptions) {
this.u1 = new ATtiny3227_M();
this.c1 = new this.passives.Capacitor({ value: '100nF' });
this.net(this.u1.VCC, this.c1.pin(1));
this.net(this.u1.GND, this.c1.pin(2));
if (options.vin) {
this.net(options.vin.power, this.u1.VCC);
this.net(options.vin.gnd, this.u1.GND);
}
}
}Inside build()
| Member | Purpose |
|---|---|
| build(options) | required — create the parts, wire them, position them. Runs once at construction |
| this.passives | Passive classes bound to one size — passiveSize option, ’0603’ default |
| this.net(...pins) | Connects pins inside the package |
| this.track() | Manual tracks — register them with this.add(...) so they’re collected |
| this.via(at, size?, drill?) | A via inside the package |
| this.add(...items) | Add anything the auto-collector can’t see (tracks) |
| this.components | Everything the package placed — consumers spread it into create() |
Collection
Every Component or TrackBuilder assigned to a property of this during build() is collected automatically, offset by the
consumer's x/y, and grouped under the package name:
export class TinyBoard extends Package {
declare r1: Component;
build(options) {
this.r1 = new this.passives.Resistor({ value: '10kohm' });
// no this.add() needed — assigned properties are collected automatically
}
}The consumer's view
let board = new TinyBoard({ pcb: typecad, x: 40, y: 30, vin: usb_power });
typecad.create(...board.components);publishing
Packages are ordinary npm packages — version them, tag them, publish them publicly or to a private registry. Semantic versions mean consumers can pin and update hardware like any dependency.
Example first
npm version patch
npm publish --access publicUtility packages — BOM post-processors, fab exporters, lint rules — are the same story with
no hardware: plain npm packages whose code imports the consumer's PCB programmatically.