EnglishGitHub
Prototyppackages/cli/src

CLI

@ralphschuler/cli

Verarbeitet validierte argv-Befehle mit abgewarteter Middleware.

argvcommandsmiddleware

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.

Im Repository

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?

  1. Erstes Token gegen eine Command-Registry auflösen.
  2. Restliche Tokens quote-aware in Positionals und Flags zerlegen.
  3. Rohwerte per Command-Schema konvertieren, Defaults setzen und validieren.
  4. Middleware als Onion-Kette ausführen und zuletzt den Command-Handler aufrufen.
  5. 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.

  1. Definiere Commands als Datenobjekte mit Parsern für jeden Wert.
  2. Parse argv ohne Seiteneffekte in einen Context oder einen typisierten Fehler.
  3. Komponiere Middleware und injiziere stdout, stderr und Exit-Verhalten.
Konzeptskizze · kann ausgelassene Helfer verwenden
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.
Wichtig

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