Seiten
Inhalt
COMP_cli_adr-001 – Command-Registrierung via Autodiscovery
Status: ACCEPTED Datum: 2026-03-16 Autor: xPulse
Kontext
@xpulse/cli braucht einen Mechanismus um Commands aus verschiedenen xParts
zusammenzuführen ohne von ihnen abhängen zu müssen – und ohne Boilerplate
in jedem xPart.
Zusätzlich soll ein Command vollständig von CLI-Internals abstrahiert sein:
kein Import von @xpulse/event, kein manuelles Emittieren von cli:done,
kein Wissen über Timeout-Mechanismen.
Entscheidung
@xpulse/cli ist ein Runner – kein Command-Owner.
- Jedes xPart legt Command-Klassen unter
src/commands/ab @xpulse/clidiscovert beim Start allenode_modules/@xpulse/*/src/commands/*.js- Gefundene
Command-Subklassen werden automatisch registriert - Kein
cli.jsim xPart-Root nötig
| @xpulse/cli discovert → @xpulse/doc/src/commands/FetchCommand.js |
| → @xpulse/config/src/commands/ConfigShowCommand.js |
Command-Contract
| import { Command } from '@xpulse/cli/command'; |
| export class FetchCommand extends Command { |
| configure() { |
| this |
| .setName('doc:fetch') |
| .setAlias('df') |
| .setDescription('Docs via git archive fetchen') |
| .addArgument('tool', null, 'Tool name') |
| .addOption('tag', null, 'Git tag') |
| .addFlag('dry-run', false, 'Dry run'); |
| } |
| async run(input, output) { |
| while (!done) { |
| await doWork(); |
| this.keepAlive(); // ← kein Event-Import nötig |
| } |
| output.success('Fertig!'); |
| return 0; // ← Exit Code statt cli:done Event |
| } |
| } |
Vollständige Abstraktion
Ein Command importiert nichts von @xpulse/event und weiß nichts von
CLI-Internals. @xpulse/cli übernimmt alles:
| Aufgabe | Wer macht es |
|---|---|
| Autodiscovery | @xpulse/cli |
| Input aufbauen | @xpulse/cli |
| Output aufbauen | @xpulse/cli |
keepAlive-Funktion injizieren |
@xpulse/cli |
| Timer starten + stoppen | @xpulse/cli |
process.exit(code) |
@xpulse/cli |
| Logik implementieren | xPart |
Exit Codes
run() gibt einen numerischen Exit Code zurück – bekanntes Unix-Schema:
| Code | Bedeutung |
|---|---|
0 |
Erfolg |
1 |
Fehler |
2 |
Misuse – falsche oder fehlende Argumente |
@xpulse/cli ruft process.exit(exitCode) nach run() auf.
Kein cli:done Event – run() resolved und der Prozess endet.
`cli:keepalive` – intern
cli:keepalive existiert weiterhin als internes Event in @xpulse/cli.
xParts sehen es nicht – sie rufen this.keepAlive() auf, die Basisklasse
emittiert das Event intern via eine injizierte Funktion.
`--help` + `--list`
| xpulse --help # alle Commands mit Name, Alias, Description |
| xpulse --help doc:fetch # Command-Detail mit Arguments, Options, Flags, Beispiel |
| xpulse --list # nur Namen, maschinenlesbar – für Autocomplete/Scripting |
Dependencies
| @xpulse/cli → @xpulse/event ← intern für keepAlive |
Begründung
- Single Responsibility – jedes xPart besitzt seine Commands vollständig
- Zero Coupling –
@xpulse/clikennt keine anderen xParts - Vollständige Abstraktion – Command weiß nichts von Events, Timeouts oder
Prozess-Beendigung;
run()gibt einfach einen Exit Code zurück - Kein Boilerplate – kein
cli.js, keincli:done, kein Event-Import - Self-describing –
configure()hält alles: Name, Alias, Description, Help-Text, Arguments, Options, Flags - Bekanntes Schema – Exit Codes 0/1/2 sind Unix-Standard, jeder kennt sie
keepAlive()injiziert statt importiert – Command bleibt entkoppelt, CLI bleibt Herr über den Timer--listfür Tooling – maschinenlesbare Ausgabe ermöglicht Shell-Autocomplete und externe Tools ohne HTML-Parsing von--help
Konsequenzen
- Jedes xPart das CLI-Commands anbietet, legt sie unter
src/commands/ab - Eine Datei = eine Command-Klasse (Convention)
run()gibt immer einen Exit Code zurück (0/1/2)this.keepAlive()für long-running Tasks – kein Event-Import nötig@xpulse/cliist Dependency in xParts die Commands schreibenxpulse --helpundxpulse --listsind dynamisch – zeigen nur was installiert ist
Alternativen die verworfen wurden
cli.jsim Root – Boilerplate, manuelles Event-Binding in jedem xPartcli:doneEvent vom Command – Command muss@xpulse/eventimportieren; Exit Code als Return-Wert ist einfacher und universellerstatic name/description– nicht erweiterbar für Arguments/Options/Flags/Aliascli:keepalivedirekt im Command – Command muss Event-System kennen;this.keepAlive()via Injection ist sauberer- Harter Timeout ohne Keepalive – würde long-running Tasks abbrechen
@xpulse/configals Dependency – CLI ist global, kein garantierter Projekt-Kontext