01 · Problem
Wofür braucht man das?
Eine CLI braucht einen deterministischen Parser, typisierte Wertkonvertierung, verständliche Fehler, Help-Ausgabe und einen klaren Übergang zum Command-Handler.
02 · Denkmodell
Das mentale Modell
Registry wählt den Command, Parser erzeugt einen validierten Context, Middleware umschließt den Handler und ein Renderer übersetzt Fehler in Exit-Code und Hilfe.
Verarbeitet validierte argv-Befehle mit abgewarteter Middleware. argv-Arrays erhalten Shell-Quoting. Interaktive Eingabe und Shell-Completion sind separate Adapter.
03 · Kontrollfluss
Was passiert in welcher Reihenfolge?
- Erstes Token gegen eine Command-Registry auflösen.
- Restliche Tokens quote-aware in Positionals und Flags zerlegen.
- Rohwerte per Command-Schema konvertieren, Defaults setzen und validieren.
- Middleware als Onion-Kette ausführen und zuletzt den Command-Handler aufrufen.
- Fehler in stabile Exit-Codes und gezielte Hilfe übersetzen.
04 · Bauteile
Die entscheidenden Verträge
Cli / CommandHandlerRegistriert und verarbeitet Befehle explizit.ICommand / IArgument / IFlagBeschreibt Handler und validierte Eingaben.use(middleware)Wartet Middleware um den Befehl herum ab.05 · Selbst bauen
Selbst implementieren
Halte Tokenisierung, Schemaauswertung und Ausführung getrennt – so lässt sich jede Schicht ohne Prozessstart testen.
- Definiere Commands als Datenobjekte mit Parsern für jeden Wert.
- Parse argv ohne Seiteneffekte in einen Context oder einen typisierten Fehler.
- Komponiere Middleware und injiziere stdout, stderr und Exit-Verhalten.
type Command = {
name: string;
parse(tokens: string[]): Record<string, unknown>;
run(context: Record<string, unknown>): Promise<void>;
};
async function runCli(argv: string[], commands: Map<string, Command>) {
const [name, ...tokens] = argv;
const command = commands.get(name);
if (!command) throw new Error("Unknown command: " + name);
const context = command.parse(tokens);
await command.run(context);
}06 · Verifizieren
Was solltest du testen?
- Pflichtargumente, Defaults, lange und kurze Flags werden deterministisch geparst.
- Unbekannte Commands, Flags und zusätzliche Positionals liefern gezielte Fehler.
- Middleware-Reihenfolge, Async-Fehler und Exit-Code-Mapping werden mit injizierten Streams geprüft.
Selbst ausprobieren
Formuliere die erste Prüfaussage als fehlschlagenden Test, implementiere die kleinste Korrektur und ergänze einen Grenzfall.
Pflichtargumente, Defaults, lange und kurze Flags werden deterministisch geparst.
Ansatz vergleichen
Dein Test sollte beim gezielten Verletzen der Invariante fehlschlagen und nach der Korrektur wieder bestehen. Halte den Aufbau deterministisch und prüfe ein beobachtbares Ergebnis.
07 · Grenzen
Kompromisse und Stolperfallen
- Shell-Quoting ist meist bereits vom Betriebssystem verarbeitet; ein eigener Stringparser braucht andere Regeln.
- Automatische Typmagie erschwert Fehlermeldungen – explizite Value-Parser sind besser testbar.
argv-Arrays erhalten Shell-Quoting. Interaktive Eingabe und Shell-Completion sind separate Adapter.
08 · Weiterdenken
Quellcode und verwandte Ideen
yarn tsx packages/cli/examples/basic.ts