Adding a firmware target
A new firmware is a new Strategy: implement KeymapCompiler, register it, declare its capabilities, and wire up the bundle. The canonical ConfigKeymap already gives you a fully-normalized document — you only write the lowering.
The steps below assume a new target — call it myfw.
1. Add the target id
Target is the compiler-target union in types.ts:
export type Target = 'zmk' | 'qmk' | 'keychron' | 'myfw'TypeScript will now flag every exhaustive switch and Record<Target, …> that needs a new arm — CAPABILITY_MATRIX, defaults, etc. Work through them.
2. Implement the compiler
Create compilers/myfw.ts (or a compilers/myfw/ dir if it needs submodules). Write an emit function and register the strategy:
import { runCompile, registerCompiler, type KeymapCompiler } from '../compiler'
import type { ExportedFile } from '../../types'
import type { DiagnosticBag } from '../diagnostics'
import type { ConfigKeymap } from '../types'
function emit(config: ConfigKeymap, diag: DiagnosticBag): ExportedFile[] {
// lower config.layers[].bindings (CanonAction) to your firmware's format;
// push a diag.warn(...) and emit a no-op for anything you can't support yet.
return [
/* ExportedFile[] */
]
}
const compiler: KeymapCompiler = {
target: 'myfw',
compile: (config) => runCompile(config, emit),
}
registerCompiler(compiler)Lean on the existing helpers: keycode spellings from keycodes.ts / names.ts, matrix from matrix.ts (materializeMatrix / deriveMatrix), controller from controller.ts, pins from pinmaps.ts.
3. Register it on load
Add the side-effect import to the barrel so the compiler self-registers:
// src/firmware/config/index.ts
import './compilers/zmk'
import './compilers/qmk'
import './compilers/myfw'4. Declare capabilities
Add a myfw arm to CAPABILITY_MATRIX in capabilities.ts — the lighting axes, output actions and behaviors your firmware can actually codegen. Compilers consult this to gate features (warn + no-op) instead of scattering if (target === …) checks. Update resolveAllowedTargets if the target maps to a connected-device firmware family.
5. Make it selectable & buildable
firmwareTargets.ts— add aBuilderFirmwareTargetdescriptor (id, name, blurb,wireless) so the builder's Identity panel offers it.bundle.ts— add a branch inbuildProjectBundlethat wraps your compiler's files in a repo skeleton + CI workflow + README. Reuse the existing ZMK/QMK bundle functions as a template.completeness.ts— add the readiness checks (what must be set before the generated project builds).firmwareConf.ts— derive any per-firmware config files if needed.
6. Test
Add fixtures and tests under src/firmware/config/__tests__/. The existing compiler.test.ts / bundle.test.ts show the shape: compile the demo config, assert on the emitted files and diagnostics. Run:
pnpm test
pnpm typecheckChecklist
- [ ]
Targetunion extended (types.ts) - [ ] Compiler implemented +
registerCompilercalled - [ ] Side-effect import added to
index.ts - [ ]
CAPABILITY_MATRIXarm added - [ ] Builder descriptor in
firmwareTargets.ts - [ ]
buildProjectBundlebranch + readiness checks - [ ] Tests +
pnpm typecheckgreen
