# dispatch ## Purpose Per-file orchestration extracted from the bin entry. Reads a single input file from disk, routes binary inputs (TTF/OTF/WOFF/WOFF2) through the injected `FontConverter` to produce a `TypefaceFont`, routes `.json` inputs through pass-through parsing (no conversion), then either serialises the `TypefaceFont` back to JSON (when `--format=json`) or packs it via `BinaryFontPacker` and writes the resulting `.btf`. Progress, skip, and error events are streamed through the injected printer / logger. Lives in its own module so the entry can stay small enough to satisfy the file-size budget while keeping a single round-trip testable in isolation. ## Features - Discriminates input by extension: `.ttf`/`.otf`/`.woff`/`.woff2` goes to the converter; `.json` goes to pass-through validation. - Computes destination paths honouring `--preserve-structure` (mirror the input tree under the output root) vs the flat default. - Honours `--force`: existing outputs are skipped silently unless `--force` is set, in which case they are overwritten. - Captures any throwable from the converter or packer per file, prints a failure line, logs through `LoggerLike`, and returns `{ ok: false }` so the batch can continue and the entry exits `1`. ## Usage: Basic ```ts import { FontConverter } from "@suaveplan/font-convert"; import { BinaryFontPacker } from "../packer/binary-font-packer/binary-font-packer.js"; import { dispatchOne } from "./dispatch.js"; const outcome = await dispatchOne( "./fonts/Inter-Regular.ttf", new FontConverter(), new BinaryFontPacker(), options, "./fonts", "./out", deps ); // outcome.ok === true, outcome.compressedBytes > 0 ``` ## Usage: Advanced ```ts const failures: string[] = []; for (const file of files) { const r = await dispatchOne(file, converter, packer, options, root, out, deps); if (!r.ok) failures.push(file); } if (failures.length > 0) { deps.logger.error("font-compress.cli.batch_partial", { failures }); } ``` ## API Reference | Name | Signature | Notes | |---|---|---| | `dispatchOne` | `(file, converter, packer, options, inputRoot, outputRoot, deps) => Promise` | Never throws; all failures are reported via the returned outcome. | | `FileOutcome` | `{ ok; originalBytes; compressedBytes }` | `compressedBytes === 0` means the file was skipped without overwrite. | | `DispatchDeps` | `{ logger; stdout; stderr }` | Injected so a test can capture stream output. | ## Implementation Notes Destination resolution intentionally does NOT call `path.relative` — prefix-strip plus `replace(/^[\\/]+/)` is enough and lets us mirror the upstream legacy semantics where Windows-style separators round-trip unchanged. The dispatcher always creates the parent directory with `mkdir({ recursive: true })` before writing, so users do not need to pre-create the output tree. JSON inputs are validated by `JSON.parse` only — the typeface schema is enforced downstream by `BinaryFontPacker`, which throws `FontCompressError(PACK_FAILED)` for malformed shapes, and the catch arm here converts that into a clean per-file failure line.