GitHub
Skizzepackages/cli/src

CLI

@ralphschuler/cli

Zerlegt Kommandozeilen-Tokens in Command, Arguments und Flags und führt sie durch eine Middleware-Kette.

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

Cli ist leer; mehrere benötigte Interfaces und Basisklassen fehlen, HelloWorld widerspricht den vorgesehenen Typen und es existiert weder Export noch bin-Einstieg. Das Package kompiliert nicht.

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 (leer) Die vorhandene exportlose Klasse enthält noch keine Implementierung.
CommandSpec (beabsichtigt) Soll Name, Arguments, Flags, Validatoren und run-Handler verbinden.
HelloWorldCommand (unvollständig) Skizziert eine Command-Definition, kann mit den fehlenden Basistypen aber nicht bauen.

05 · Build it yourself

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.
minimal.ts · unabhängig vom Package
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 du testen solltest

  • 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.

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

Der aktuelle Repository-Code ist eine Entwurfsskizze, keine nutzbare CLI-Library.

08 · Weiterdenken

Quellcode und Nachbarn

Originalcode auf GitHub ansehen