# Metro CLI-Tool

<div id="introduction"></div>

## Einleitung

Metro ist ein CLI-Tool, das unter der Haube des Nylo-Frameworks arbeitet.
Es bietet viele hilfreiche Werkzeuge zur Beschleunigung der Entwicklung.

<div id="install"></div>

## Installieren

Wenn Sie ein neues Nylo-Projekt mit `nylo init` erstellen, wird der `metro`-Befehl automatisch fuer Ihr Terminal konfiguriert. Sie koennen ihn sofort in jedem Nylo-Projekt verwenden.

Fuehren Sie `metro` aus Ihrem Projektverzeichnis aus, um alle verfuegbaren Befehle zu sehen:

``` bash
metro
```

Sie sollten eine Ausgabe aehnlich der folgenden sehen.

``` bash
Metro - Nylo's Companion to Build Flutter apps by Anthony Gordon

Usage:
    command [options] [arguments]

Options
    -h

All commands:

[Widget Commands]
  make:page
  make:stateful_widget
  make:stateless_widget
  make:state_managed_widget
  make:navigation_hub
  make:journey_widget
  make:bottom_sheet_modal
  make:button
  make:form

[Helper Commands]
  make:model
  make:provider
  make:api_service
  make:controller
  make:event
  make:theme
  make:route_guard
  make:config
  make:interceptor
  make:command
  make:env
  make:key
```

<div id="make-controller"></div>

## Controller erstellen

- [Einen neuen Controller erstellen](#making-a-new-controller "Einen neuen Controller mit Metro erstellen")
- [Controller erzwungen erstellen](#forcefully-make-a-controller "Einen Controller erzwungen mit Metro erstellen")
<div id="making-a-new-controller"></div>

### Einen neuen Controller erstellen

Sie koennen einen neuen Controller erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:controller profile_controller
```

Dies erstellt einen neuen Controller, falls er noch nicht im Verzeichnis `lib/app/controllers/` existiert.

<div id="forcefully-make-a-controller"></div>

### Controller erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandener Controller ueberschrieben, falls er bereits existiert.

``` bash
metro make:controller profile_controller --force
```

<div id="make-model"></div>

## Model erstellen

- [Ein neues Model erstellen](#making-a-new-model "Ein neues Model mit Metro erstellen")
- [Model aus JSON erstellen](#make-model-from-json "Ein neues Model aus JSON mit Metro erstellen")
- [Model erzwungen erstellen](#forcefully-make-a-model "Ein Model erzwungen mit Metro erstellen")
<div id="making-a-new-model"></div>

### Ein neues Model erstellen

Sie koennen ein neues Model erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:model product
```

Das neu erstellte Model wird in `lib/app/models/` abgelegt.

<div id="make-model-from-json"></div>

### Ein Model aus JSON erstellen

**Argumente:**

Mit dem `--json`- oder `-j`-Flag wird ein neues Model aus einer JSON-Nutzlast erstellt.

``` bash
metro make:model product --json
```

Dann koennen Sie Ihr JSON in das Terminal einfuegen und es wird ein Model fuer Sie generiert.

<div id="forcefully-make-a-model"></div>

### Model erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandenes Model ueberschrieben, falls es bereits existiert.

``` bash
metro make:model product --force
```

<div id="make-page"></div>

## Seite erstellen

- [Eine neue Seite erstellen](#making-a-new-page "Eine neue Seite mit Metro erstellen")
- [Eine Seite mit Controller erstellen](#create-a-page-with-a-controller "Eine neue Seite mit Controller mit Metro erstellen")
- [Eine Auth-Seite erstellen](#create-an-auth-page "Eine neue Auth-Seite mit Metro erstellen")
- [Eine initiale Seite erstellen](#create-an-initial-page "Eine neue initiale Seite mit Metro erstellen")
- [Seite erzwungen erstellen](#forcefully-make-a-page "Eine Seite erzwungen mit Metro erstellen")

<div id="making-a-new-page"></div>

### Eine neue Seite erstellen

Sie koennen eine neue Seite erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:page product_page
```

Dies erstellt eine neue Seite, falls sie noch nicht im Verzeichnis `lib/resources/pages/` existiert.

<div id="create-a-page-with-a-controller"></div>

### Eine Seite mit Controller erstellen

Sie koennen eine neue Seite mit einem Controller erstellen, indem Sie Folgendes im Terminal ausfuehren.

**Argumente:**

Mit dem `--controller`- oder `-c`-Flag wird eine neue Seite mit einem Controller erstellt.

``` bash
metro make:page product_page -c
```

<div id="create-an-auth-page"></div>

### Eine Auth-Seite erstellen

Sie koennen eine neue Auth-Seite erstellen, indem Sie Folgendes im Terminal ausfuehren.

**Argumente:**

Mit dem `--auth`- oder `-a`-Flag wird eine neue Auth-Seite erstellt.

``` bash
metro make:page login_page -a
```

<div id="create-an-initial-page"></div>

### Eine initiale Seite erstellen

Sie koennen eine neue initiale Seite erstellen, indem Sie Folgendes im Terminal ausfuehren.

**Argumente:**

Mit dem `--initial`- oder `-i`-Flag wird eine neue initiale Seite erstellt.

``` bash
metro make:page home_page -i
```

<div id="forcefully-make-a-page"></div>

### Seite erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird eine vorhandene Seite ueberschrieben, falls sie bereits existiert.

``` bash
metro make:page product_page --force
```

<div id="make-stateless-widget"></div>

## Stateless Widget erstellen

- [Ein neues Stateless Widget erstellen](#making-a-new-stateless-widget "Ein neues Stateless Widget mit Metro erstellen")
- [Stateless Widget erzwungen erstellen](#forcefully-make-a-stateless-widget "Ein Stateless Widget erzwungen mit Metro erstellen")
<div id="making-a-new-stateless-widget"></div>

### Ein neues Stateless Widget erstellen

Sie koennen ein neues Stateless Widget erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:stateless_widget product_rating_widget
```

Das obige erstellt ein neues Widget, falls es noch nicht im Verzeichnis `lib/resources/widgets/` existiert.

Alle `make:*`-Befehle akzeptieren einen Pfadtrenner im Namen, um die Datei in ein Unterverzeichnis zu legen:

``` bash
metro make:stateless_widget login/BrandPanel
```

Dadurch wird das Widget unter `lib/resources/widgets/login/brand_panel.dart` erstellt.

<div id="forcefully-make-a-stateless-widget"></div>

### Stateless Widget erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandenes Widget ueberschrieben, falls es bereits existiert.

``` bash
metro make:stateless_widget product_rating_widget --force
```

<div id="make-stateful-widget"></div>

## Stateful Widget erstellen

- [Ein neues Stateful Widget erstellen](#making-a-new-stateful-widget "Ein neues Stateful Widget mit Metro erstellen")
- [Stateful Widget erzwungen erstellen](#forcefully-make-a-stateful-widget "Ein Stateful Widget erzwungen mit Metro erstellen")

<div id="making-a-new-stateful-widget"></div>

### Ein neues Stateful Widget erstellen

Sie koennen ein neues Stateful Widget erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:stateful_widget product_rating_widget
```

Das obige erstellt ein neues Widget, falls es noch nicht im Verzeichnis `lib/resources/widgets/` existiert.

<div id="forcefully-make-a-stateful-widget"></div>

### Stateful Widget erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandenes Widget ueberschrieben, falls es bereits existiert.

``` bash
metro make:stateful_widget product_rating_widget --force
```

<div id="make-journey-widget"></div>

## Journey Widget erstellen

- [Ein neues Journey Widget erstellen](#making-a-new-journey-widget "Ein neues Journey Widget mit Metro erstellen")
- [Journey Widget erzwungen erstellen](#forcefully-make-a-journey-widget "Ein Journey Widget erzwungen mit Metro erstellen")

<div id="making-a-new-journey-widget"></div>

### Ein neues Journey Widget erstellen

Sie koennen ein neues Journey Widget erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:journey_widget product_journey --parent="[NAVIGATION_HUB]"

# Full example if you have a BaseNavigationHub
metro make:journey_widget welcome,user_dob,user_photos --parent="Base"
```

Das obige erstellt ein neues Widget, falls es noch nicht im Verzeichnis `lib/resources/widgets/` existiert.

Das `--parent`-Argument wird verwendet, um das uebergeordnete Widget anzugeben, zu dem das neue Journey Widget hinzugefuegt wird.

Beispiel

``` bash
metro make:navigation_hub onboarding
```

Fuegen Sie als Naechstes die neuen Journey Widgets hinzu.
``` bash
metro make:journey_widget welcome,user_dob,user_photos --parent="onboarding"
```

<div id="forcefully-make-a-journey-widget"></div>

### Journey Widget erzwungen erstellen
**Argumente:**
Mit dem `--force`- oder `-f`-Flag wird ein vorhandenes Widget ueberschrieben, falls es bereits existiert.

``` bash
metro make:journey_widget product_journey --force --parent="[YOUR_NAVIGATION_HUB]"
```

<div id="make-api-service"></div>

## API Service erstellen

- [Einen neuen API Service erstellen](#making-a-new-api-service "Einen neuen API Service mit Metro erstellen")
- [Einen neuen API Service mit Model erstellen](#making-a-new-api-service-with-a-model "Einen neuen API Service mit Model mit Metro erstellen")
- [API Service erzwungen erstellen](#forcefully-make-an-api-service "Einen API Service erzwungen mit Metro erstellen")

<div id="making-a-new-api-service"></div>

### Einen neuen API Service erstellen

Sie koennen einen neuen API Service erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:api_service user_api_service
```

Der neu erstellte API Service wird in `lib/app/networking/` abgelegt.

<div id="making-a-new-api-service-with-a-model"></div>

### Einen neuen API Service mit Model erstellen

Sie koennen einen neuen API Service mit einem Model erstellen, indem Sie Folgendes im Terminal ausfuehren.

**Argumente:**

Mit der `--model`- oder `-m`-Option wird ein neuer API Service mit einem Model erstellt.

``` bash
metro make:api_service user --model="User"
```

Der neu erstellte API Service wird in `lib/app/networking/` abgelegt.

### API Service erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandener API Service ueberschrieben, falls er bereits existiert.

``` bash
metro make:api_service user --force
```

<div id="make-event"></div>

## Event erstellen

- [Ein neues Event erstellen](#making-a-new-event "Ein neues Event mit Metro erstellen")
- [Event erzwungen erstellen](#forcefully-make-an-event "Ein Event erzwungen mit Metro erstellen")

<div id="making-a-new-event"></div>

### Ein neues Event erstellen

Sie koennen ein neues Event erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:event login_event
```

Dies erstellt ein neues Event in `lib/app/events`.

Verwenden Sie einen Pfadtrenner, um das Event in ein Unterverzeichnis zu organisieren:

``` bash
metro make:event auth/login_event
```

Dadurch wird das Event unter `lib/app/events/auth/login_event.dart` erstellt.

<div id="forcefully-make-an-event"></div>

### Event erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandenes Event ueberschrieben, falls es bereits existiert.

``` bash
metro make:event login_event --force
```

<div id="make-provider"></div>

## Provider erstellen

- [Einen neuen Provider erstellen](#making-a-new-provider "Einen neuen Provider mit Metro erstellen")
- [Provider erzwungen erstellen](#forcefully-make-a-provider "Einen Provider erzwungen mit Metro erstellen")

<div id="making-a-new-provider"></div>

### Einen neuen Provider erstellen

Erstellen Sie neue Provider in Ihrer Anwendung mit dem folgenden Befehl.

``` bash
metro make:provider firebase_provider
```

Der neu erstellte Provider wird in `lib/app/providers/` abgelegt.

Verwenden Sie einen Pfadtrenner, um den Provider in ein Unterverzeichnis zu organisieren:

``` bash
metro make:provider integrations/firebase_provider
```

Dadurch wird der Provider unter `lib/app/providers/integrations/firebase_provider.dart` erstellt.

<div id="forcefully-make-a-provider"></div>

### Provider erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandener Provider ueberschrieben, falls er bereits existiert.

``` bash
metro make:provider firebase_provider --force
```

<div id="make-theme"></div>

## Theme erstellen

- [Ein neues Theme erstellen](#making-a-new-theme "Ein neues Theme mit Metro erstellen")
- [Theme erzwungen erstellen](#forcefully-make-a-theme "Ein Theme erzwungen mit Metro erstellen")

<div id="making-a-new-theme"></div>

### Ein neues Theme erstellen

Sie koennen Themes erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:theme bright_theme
```

Dies erstellt ein neues Theme in `lib/resources/themes/`.

<div id="forcefully-make-a-theme"></div>

### Theme erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandenes Theme ueberschrieben, falls es bereits existiert.

``` bash
metro make:theme bright_theme --force
```

<div id="make-forms"></div>

## Formulare erstellen

- [Ein neues Formular erstellen](#making-a-new-form "Ein neues Formular mit Metro erstellen")
- [Formular erzwungen erstellen](#forcefully-make-a-form "Ein Formular erzwungen mit Metro erstellen")

<div id="making-a-new-form"></div>

### Ein neues Formular erstellen

Sie koennen ein neues Formular erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:form car_advert_form
```

Dies erstellt ein neues Formular in `lib/app/forms`.

Verwenden Sie einen Pfadtrenner, um das Formular in ein Unterverzeichnis zu organisieren:

``` bash
metro make:form checkout/car_advert_form
```

Dadurch wird das Formular unter `lib/app/forms/checkout/car_advert_form.dart` erstellt.

<div id="forcefully-make-a-form"></div>

### Formular erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandenes Formular ueberschrieben, falls es bereits existiert.

``` bash
metro make:form login_form --force
```

<div id="make-route-guard"></div>

## Route Guard erstellen

- [Einen neuen Route Guard erstellen](#making-a-new-route-guard "Einen neuen Route Guard mit Metro erstellen")
- [Route Guard erzwungen erstellen](#forcefully-make-a-route-guard "Einen Route Guard erzwungen mit Metro erstellen")

<div id="making-a-new-route-guard"></div>

### Einen neuen Route Guard erstellen

Sie koennen einen Route Guard erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:route_guard premium_content
```

Dies erstellt einen neuen Route Guard in `lib/app/route_guards`.

Verwenden Sie einen Pfadtrenner, um den Guard in ein Unterverzeichnis zu organisieren:

``` bash
metro make:route_guard subscriptions/premium_content
```

Dadurch wird der Guard unter `lib/app/route_guards/subscriptions/premium_content.dart` erstellt.

<div id="forcefully-make-a-route-guard"></div>

### Route Guard erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird ein vorhandener Route Guard ueberschrieben, falls er bereits existiert.

``` bash
metro make:route_guard premium_content --force
```

<div id="make-config-file"></div>

## Config-Datei erstellen

- [Eine neue Config-Datei erstellen](#making-a-new-config-file "Eine neue Config-Datei mit Metro erstellen")
- [Config-Datei erzwungen erstellen](#forcefully-make-a-config-file "Eine Config-Datei erzwungen mit Metro erstellen")

<div id="making-a-new-config-file"></div>

### Eine neue Config-Datei erstellen

Sie koennen eine neue Config-Datei erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:config shopping_settings
```

Dies erstellt eine neue Config-Datei in `lib/app/config`.

<div id="forcefully-make-a-config-file"></div>

### Config-Datei erzwungen erstellen

**Argumente:**

Mit dem `--force`- oder `-f`-Flag wird eine vorhandene Config-Datei ueberschrieben, falls sie bereits existiert.

``` bash
metro make:config app_config --force
```


<div id="make-command"></div>

## Command erstellen

- [Einen neuen Command erstellen](#making-a-new-command "Einen neuen Command mit Metro erstellen")
- [Command erzwungen erstellen](#forcefully-make-a-command "Einen Command erzwungen mit Metro erstellen")

<div id="making-a-new-command"></div>

### Einen neuen Command erstellen

Sie koennen einen neuen Command erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:command my_command
```

Dies erstellt einen neuen Command in `lib/app/commands`.

<div id="forcefully-make-a-command"></div>

### Command erzwungen erstellen

**Argumente:**
Mit dem `--force`- oder `-f`-Flag wird ein vorhandener Command ueberschrieben, falls er bereits existiert.

``` bash
metro make:command my_command --force
```


<div id="make-state-managed-widget"></div>

## State Managed Widget erstellen

Sie koennen ein neues State Managed Widget erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:state_managed_widget product_rating_widget
```

Das obige erstellt ein neues Widget in `lib/resources/widgets/`.

Mit dem `--force`- oder `-f`-Flag wird ein vorhandenes Widget ueberschrieben, falls es bereits existiert.

``` bash
metro make:state_managed_widget product_rating_widget --force
```

<div id="make-navigation-hub"></div>

## Navigation Hub erstellen

Sie koennen einen neuen Navigation Hub erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:navigation_hub dashboard
```

Dies erstellt einen neuen Navigation Hub in `lib/resources/pages/` und fuegt die Route automatisch hinzu.

**Argumente:**

| Flag | Kurz | Beschreibung |
|------|-------|-------------|
| `--auth` | `-a` | Als Auth-Seite erstellen |
| `--initial` | `-i` | Als initiale Seite erstellen |
| `--force` | `-f` | Ueberschreiben, falls vorhanden |

``` bash
# Create as the initial page
metro make:navigation_hub dashboard --initial
```

<div id="make-bottom-sheet-modal"></div>

## Bottom Sheet Modal erstellen

Sie koennen ein neues Bottom Sheet Modal erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:bottom_sheet_modal payment_options
```

Dies erstellt ein neues Bottom Sheet Modal in `lib/resources/widgets/`.

Mit dem `--force`- oder `-f`-Flag wird ein vorhandenes Modal ueberschrieben, falls es bereits existiert.

``` bash
metro make:bottom_sheet_modal payment_options --force
```

<div id="make-button"></div>

## Button erstellen

Sie koennen ein neues Button-Widget erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:button checkout_button
```

Dies erstellt ein neues Button-Widget in `lib/resources/widgets/`.

Mit dem `--force`- oder `-f`-Flag wird ein vorhandener Button ueberschrieben, falls er bereits existiert.

``` bash
metro make:button checkout_button --force
```

<div id="make-interceptor"></div>

## Interceptor erstellen

Sie koennen einen neuen Netzwerk-Interceptor erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:interceptor auth_interceptor
```

Dies erstellt einen neuen Interceptor in `lib/app/networking/dio/interceptors/`.

Mit dem `--force`- oder `-f`-Flag wird ein vorhandener Interceptor ueberschrieben, falls er bereits existiert.

``` bash
metro make:interceptor auth_interceptor --force
```

<div id="make-env-file"></div>

## Env-Datei erstellen

Sie koennen eine neue Umgebungsdatei erstellen, indem Sie Folgendes im Terminal ausfuehren.

``` bash
metro make:env .env.staging
```

Dies erstellt eine neue `.env`-Datei in Ihrem Projektstammverzeichnis.

<div id="make-key"></div>

## Key generieren

Generieren Sie einen sicheren `APP_KEY` fuer die Umgebungsverschluesselung. Dieser wird fuer verschluesselte `.env`-Dateien in v7 verwendet.

``` bash
metro make:key
```

**Argumente:**

| Flag / Option | Kurz | Beschreibung |
|---------------|-------|-------------|
| `--force` | `-f` | Vorhandenen APP_KEY ueberschreiben |
| `--file` | `-e` | Ziel-.env-Datei (Standard: `.env`) |

``` bash
# Generate key and overwrite existing
metro make:key --force

# Generate key for a specific env file
metro make:key --file=.env.production
```

<div id="build-app-icons"></div>

## App-Icons erstellen

Sie koennen alle App-Icons fuer iOS und Android generieren, indem Sie den folgenden Befehl ausfuehren.

``` bash
dart run flutter_launcher_icons:main
```

Dies verwendet die <b>flutter_icons</b>-Konfiguration in Ihrer `pubspec.yaml`-Datei.

<div id="custom-commands"></div>

## Benutzerdefinierte Commands

Benutzerdefinierte Commands ermoeglichen es Ihnen, die CLI von Nylo mit Ihren eigenen projektspezifischen Befehlen zu erweitern. Diese Funktion ermoeglicht es Ihnen, wiederkehrende Aufgaben zu automatisieren, Deployment-Workflows zu implementieren oder beliebige benutzerdefinierte Funktionalitaet direkt in die Befehlszeilentools Ihres Projekts einzufuegen.

- [Benutzerdefinierte Commands erstellen](#creating-custom-commands)
- [Benutzerdefinierte Commands ausfuehren](#running-custom-commands)
- [Optionen zu Commands hinzufuegen](#adding-options-to-custom-commands)
- [Flags zu Commands hinzufuegen](#adding-flags-to-custom-commands)
- [Hilfsmethoden](#custom-command-helper-methods)

> **Hinweis:** Sie koennen derzeit nicht nylo_framework.dart in Ihren benutzerdefinierten Commands importieren, verwenden Sie stattdessen bitte ny_cli.dart.

<div id="creating-custom-commands"></div>

## Benutzerdefinierte Commands erstellen

Um einen neuen benutzerdefinierten Command zu erstellen, koennen Sie die `make:command`-Funktion verwenden:

```bash
metro make:command current_time
```

Sie koennen mit der `--category`-Option eine Kategorie fuer Ihren Command angeben:

```bash
# Specify a category
metro make:command current_time --category="project"
```

Dies erstellt eine neue Command-Datei unter `lib/app/commands/current_time.dart` mit folgender Struktur:

``` dart
import 'package:nylo_framework/metro/ny_cli.dart';

void main(arguments) => _CurrentTimeCommand(arguments).run();

/// Aktuelle-Zeit-Command
///
/// Verwendung:
///   metro app:current_time
class _CurrentTimeCommand extends NyCustomCommand {
  _CurrentTimeCommand(super.arguments);

  @override
  CommandBuilder builder(CommandBuilder command) {
    command.addOption('format', defaultValue: 'HH:mm:ss');
    return command;
  }

  @override
  Future<void> handle(CommandResult result) async {
      final format = result.getString("format");

      // Aktuelle Zeit abrufen
      final now = DateTime.now();
      final DateFormat dateFormat = DateFormat(format);

      // Aktuelle Zeit formatieren
      final formattedTime = dateFormat.format(now);
      info("The current time is " + formattedTime);
  }
}
```

Der Command wird automatisch in der Datei `lib/app/commands/commands.json` registriert, die eine Liste aller registrierten Commands enthaelt:

```json
[
  {
    "name": "install_firebase",
    "category": "project",
    "script": "install_firebase.dart"
  },
  {
    "name": "current_time",
    "category": "app",
    "script": "current_time.dart"
  }
]
```

<div id="running-custom-commands"></div>

## Benutzerdefinierte Commands ausfuehren

Nach der Erstellung koennen Sie Ihren benutzerdefinierten Command entweder mit der Metro-Kurzform oder dem vollstaendigen Dart-Befehl ausfuehren:

```bash
metro app:current_time
```

Wenn Sie `metro` ohne Argumente ausfuehren, sehen Sie Ihre benutzerdefinierten Commands im Menue unter dem Abschnitt "Custom Commands":

```
[Custom Commands]
  app:app_icon
  app:clear_pub
  project:install_firebase
  project:deploy
```

Um Hilfe-Informationen fuer Ihren Command anzuzeigen, verwenden Sie das `--help`- oder `-h`-Flag:

```bash
metro project:install_firebase --help
```

<div id="adding-options-to-custom-commands"></div>

## Optionen zu Commands hinzufuegen

Optionen ermoeglichen es Ihrem Command, zusaetzliche Eingaben von Benutzern zu akzeptieren. Sie koennen Optionen in der `builder`-Methode hinzufuegen:

```dart
@override
CommandBuilder builder(CommandBuilder command) {

  // Option mit Standardwert hinzufuegen
  command.addOption(
    'environment',     // Optionsname
    abbr: 'e',         // Kurzform-Abkuerzung
    help: 'Target deployment environment', // Hilfetext
    defaultValue: 'development',  // Standardwert
    allowed: ['development', 'staging', 'production'] // erlaubte Werte
  );

  return command;
}
```

Greifen Sie dann in der `handle`-Methode Ihres Commands auf den Optionswert zu:

```dart
@override
Future<void> handle(CommandResult result) async {
  final environment = result.getString('environment');
  info('Deploying to $environment environment...');

  // Command-Implementierung...
}
```

Beispielverwendung:

```bash
metro project:deploy --environment=production
# oder Kurzform
metro project:deploy -e production
```

<div id="adding-flags-to-custom-commands"></div>

## Flags zu Commands hinzufuegen

Flags sind boolesche Optionen, die ein- oder ausgeschaltet werden koennen. Fuegen Sie Flags mit der `addFlag`-Methode hinzu:

```dart
@override
CommandBuilder builder(CommandBuilder command) {

  command.addFlag(
    'verbose',       // Flag-Name
    abbr: 'v',       // Kurzform-Abkuerzung
    help: 'Enable verbose output', // Hilfetext
    defaultValue: false  // standardmaessig deaktiviert
  );

  return command;
}
```

Pruefen Sie dann den Flag-Status in der `handle`-Methode Ihres Commands:

```dart
@override
Future<void> handle(CommandResult result) async {
  final verbose = result.getBool('verbose');

  if (verbose) {
    info('Verbose mode enabled');
    // Zusaetzliches Logging...
  }

  // Command-Implementierung...
}
```

Beispielverwendung:

```bash
metro project:deploy --verbose
# oder Kurzform
metro project:deploy -v
```

<div id="custom-command-helper-methods"></div>

## Hilfsmethoden

Die Basisklasse `NyCustomCommand` bietet mehrere Hilfsmethoden fuer gaengige Aufgaben:

#### Nachrichten ausgeben

Hier sind einige Methoden zum Ausgeben von Nachrichten in verschiedenen Farben:

| |  |
|-------------|-------------|
| [`info`](#custom-command-helper-formatting)      | Eine Info-Nachricht in blauem Text ausgeben |
| [`error`](#custom-command-helper-formatting)     | Eine Fehlernachricht in rotem Text ausgeben |
| [`success`](#custom-command-helper-formatting)   | Eine Erfolgsnachricht in gruenem Text ausgeben |
| [`warning`](#custom-command-helper-formatting)   | Eine Warnungsnachricht in gelbem Text ausgeben |

#### Prozesse ausfuehren

Prozesse ausfuehren und ihre Ausgabe in der Konsole anzeigen:

| |  |
|-------------|-------------|
| [`addPackage`](#custom-command-helper-add-package) | Ein Paket zur `pubspec.yaml` hinzufuegen |
| [`addPackages`](#custom-command-helper-add-packages) | Mehrere Pakete zur `pubspec.yaml` hinzufuegen |
| [`runProcess`](#custom-command-helper-run-process) | Einen externen Prozess ausfuehren und Ausgabe in der Konsole anzeigen |
| [`prompt`](#custom-command-helper-prompt)    | Benutzereingabe als Text erfassen |
| [`confirm`](#custom-command-helper-confirm)   | Eine Ja/Nein-Frage stellen und ein boolesches Ergebnis zurueckgeben |
| [`select`](#custom-command-helper-select)    | Eine Liste von Optionen praesentieren und den Benutzer eine auswaehlen lassen |
| [`multiSelect`](#custom-command-helper-multi-select) | Den Benutzer mehrere Optionen aus einer Liste auswaehlen lassen |

#### Netzwerkanfragen

Netzwerkanfragen ueber die Konsole stellen:

| |  |
|-------------|-------------|
| [`api`](#custom-command-helper-multi-select) | Einen API-Aufruf mit dem Nylo API-Client durchfuehren |


#### Ladeanzeige

Eine Ladeanzeige waehrend der Ausfuehrung einer Funktion anzeigen:

| |  |
|-------------|-------------|
| [`withSpinner`](#using-with-spinner) | Eine Ladeanzeige waehrend der Ausfuehrung einer Funktion anzeigen |
| [`createSpinner`](#manual-spinner-control) | Eine Spinner-Instanz fuer manuelle Steuerung erstellen |

#### Helfer fuer benutzerdefinierte Commands

Sie koennen auch die folgenden Hilfsmethoden verwenden, um Command-Argumente zu verwalten:

| |  |
|-------------|-------------|
| [`getString`](#custom-command-helper-get-string) | Einen String-Wert aus den Command-Argumenten abrufen |
| [`getBool`](#custom-command-helper-get-bool)   | Einen booleschen Wert aus den Command-Argumenten abrufen |
| [`getInt`](#custom-command-helper-get-int)    | Einen Integer-Wert aus den Command-Argumenten abrufen |
| [`sleep`](#custom-command-helper-sleep) | Die Ausfuehrung fuer eine bestimmte Dauer pausieren |


### Externe Prozesse ausfuehren

```dart
// Prozess mit Ausgabe in der Konsole ausfuehren
await runProcess('flutter build web --release');

// Prozess lautlos ausfuehren
await runProcess('flutter pub get', silent: true);

// Prozess in einem bestimmten Verzeichnis ausfuehren
await runProcess('git pull', workingDirectory: './my-project');
```

### Paketverwaltung

<div id="custom-command-helper-add-package"></div>
<div id="custom-command-helper-add-packages"></div>

```dart
// Paket zu pubspec.yaml hinzufuegen
addPackage('firebase_core', version: '^2.4.0');

// Entwicklungspaket zu pubspec.yaml hinzufuegen
addPackage('build_runner', dev: true);

// Mehrere Pakete auf einmal hinzufuegen
addPackages(['firebase_auth', 'firebase_storage', 'quickalert']);
```

<div id="custom-command-helper-formatting"></div>

### Ausgabeformatierung

```dart
// Statusmeldungen mit Farbkodierung ausgeben
info('Processing files...');    // Blauer Text
error('Operation failed');      // Roter Text
success('Deployment complete'); // Gruener Text
warning('Outdated package');    // Gelber Text
```

<div id="interactive-input-methods"></div>

## Interaktive Eingabemethoden

Die Basisklasse `NyCustomCommand` bietet mehrere Methoden zum Erfassen von Benutzereingaben im Terminal. Diese Methoden erleichtern die Erstellung interaktiver Befehlszeilen-Interfaces fuer Ihre benutzerdefinierten Commands.

<div id="custom-command-helper-prompt"></div>

### Texteingabe

```dart
String prompt(String question, {String defaultValue = ''})
```

Zeigt dem Benutzer eine Frage an und erfasst seine Textantwort.

**Parameter:**
- `question`: Die anzuzeigende Frage oder Aufforderung
- `defaultValue`: Optionaler Standardwert, wenn der Benutzer nur Enter drueckt

**Rueckgabe:** Die Eingabe des Benutzers als String oder der Standardwert, wenn keine Eingabe erfolgte

**Beispiel:**
```dart
final name = prompt('What is your project name?', defaultValue: 'my_app');
final description = prompt('Enter a project description:');
```

<div id="custom-command-helper-confirm"></div>

### Bestaetigung

```dart
bool confirm(String question, {bool defaultValue = false})
```

Stellt dem Benutzer eine Ja/Nein-Frage und gibt ein boolesches Ergebnis zurueck.

**Parameter:**
- `question`: Die Ja/Nein-Frage
- `defaultValue`: Die Standardantwort (true fuer Ja, false fuer Nein)

**Rueckgabe:** `true`, wenn der Benutzer mit Ja geantwortet hat, `false`, wenn er mit Nein geantwortet hat

**Beispiel:**
```dart
if (confirm('Would you like to continue?', defaultValue: true)) {
  // Benutzer hat bestaetigt oder Enter gedrueckt (Standardwert akzeptiert)
  await runProcess('flutter pub get');
} else {
  // Benutzer hat abgelehnt
  info('Operation canceled');
}
```

<div id="custom-command-helper-select"></div>

### Einzelauswahl

```dart
String select(String question, List<String> options, {String? defaultOption})
```

Praesentiert eine Liste von Optionen und laesst den Benutzer eine auswaehlen.

**Parameter:**
- `question`: Die Auswahlaufforderung
- `options`: Liste der verfuegbaren Optionen
- `defaultOption`: Optionale Standardauswahl

**Rueckgabe:** Die ausgewaehlte Option als String

**Beispiel:**
```dart
final environment = select(
  'Select deployment environment:',
  ['development', 'staging', 'production'],
  defaultOption: 'development'
);

info('Deploying to $environment environment...');
```

<div id="custom-command-helper-multi-select"></div>

### Mehrfachauswahl

```dart
List<String> multiSelect(String question, List<String> options)
```

Ermoeglicht dem Benutzer, mehrere Optionen aus einer Liste auszuwaehlen.

**Parameter:**
- `question`: Die Auswahlaufforderung
- `options`: Liste der verfuegbaren Optionen

**Rueckgabe:** Eine Liste der ausgewaehlten Optionen

**Beispiel:**
```dart
final packages = multiSelect(
  'Select packages to install:',
  ['firebase_auth', 'dio', 'provider', 'shared_preferences', 'path_provider']
);

if (packages.isNotEmpty) {
  info('Installing ${packages.length} packages...');
  addPackages(packages);
  await runProcess('flutter pub get');
}
```

<div id="custom-command-helper-api"></div>

## API-Hilfsmethode

Die `api`-Hilfsmethode vereinfacht Netzwerkanfragen aus Ihren benutzerdefinierten Commands.

```dart
Future<T?> api<T>(Future<T?> Function(ApiService) request) async
```

## Grundlegende Verwendungsbeispiele

### GET-Anfrage

```dart
// Daten abrufen
final userData = await api((request) =>
  request.get('https://api.example.com/users/1')
);
```

### POST-Anfrage

```dart
// Ressource erstellen
final result = await api((request) =>
  request.post(
    'https://api.example.com/items',
    data: {'name': 'New Item', 'price': 19.99}
  )
);
```

### PUT-Anfrage

```dart
// Ressource aktualisieren
final updateResult = await api((request) =>
  request.put(
    'https://api.example.com/items/42',
    data: {'name': 'Updated Item', 'price': 29.99}
  )
);
```

### DELETE-Anfrage

```dart
// Ressource loeschen
final deleteResult = await api((request) => request.delete('https://api.example.com/items/42'));
```

### PATCH-Anfrage

```dart
// Ressource teilweise aktualisieren
final patchResult = await api((request) => request.patch(
    'https://api.example.com/items/42',
    data: {'price': 24.99}
  )
);
```

### Mit Query-Parametern

```dart
// Query-Parameter hinzufuegen
final searchResults = await api((request) => request.get(
    'https://api.example.com/search',
    queryParameters: {'q': 'keyword', 'limit': 10}
  )
);
```

### Mit Spinner

```dart
// Mit Spinner fuer bessere Benutzeroberflaechenerfahrung verwenden
final data = await withSpinner(
  task: () async {
    final data = await api((request) => request.get('https://api.example.com/config'));
    // Daten verarbeiten
  },
  message: 'Loading configuration',
);
```


<div id="using-spinners"></div>

## Spinner-Funktionalitaet

Spinner bieten visuelles Feedback waehrend lang laufender Operationen in Ihren benutzerdefinierten Commands. Sie zeigen einen animierten Indikator zusammen mit einer Nachricht an, waehrend Ihr Command asynchrone Aufgaben ausfuehrt, und verbessern die Benutzererfahrung durch Anzeige von Fortschritt und Status.

- [Mit Spinner verwenden](#using-with-spinner)
- [Manuelle Spinner-Steuerung](#manual-spinner-control)
- [Beispiele](#spinner-examples)

<div id="using-with-spinner"></div>

## Mit Spinner verwenden

Die `withSpinner`-Methode ermoeglicht es Ihnen, eine asynchrone Aufgabe mit einer Spinner-Animation zu umschliessen, die automatisch startet, wenn die Aufgabe beginnt, und stoppt, wenn sie abgeschlossen ist oder fehlschlaegt:

```dart
Future<T> withSpinner<T>({
  required Future<T> Function() task,
  required String message,
  String? successMessage,
  String? errorMessage,
}) async
```

**Parameter:**
- `task`: Die auszufuehrende asynchrone Funktion
- `message`: Text, der waehrend des Spinner-Laufs angezeigt wird
- `successMessage`: Optionale Nachricht bei erfolgreichem Abschluss
- `errorMessage`: Optionale Nachricht bei Fehlschlag der Aufgabe

**Rueckgabe:** Das Ergebnis der Task-Funktion

**Beispiel:**
```dart
@override
Future<void> handle(CommandResult result) async {
  // Aufgabe mit Spinner ausfuehren
  final projectFiles = await withSpinner(
    task: () async {
      // Lang laufende Aufgabe (z.B. Projektdateien analysieren)
      await sleep(2);
      return ['pubspec.yaml', 'lib/main.dart', 'README.md'];
    },
    message: 'Analyzing project structure',
    successMessage: 'Project analysis complete',
    errorMessage: 'Failed to analyze project',
  );

  // Mit den Ergebnissen fortfahren
  info('Found ${projectFiles.length} key files');
}
```

<div id="manual-spinner-control"></div>

## Manuelle Spinner-Steuerung

Fuer komplexere Szenarien, in denen Sie den Spinner-Status manuell steuern muessen, koennen Sie eine Spinner-Instanz erstellen:

```dart
ConsoleSpinner createSpinner(String message)
```

**Parameter:**
- `message`: Text, der waehrend des Spinner-Laufs angezeigt wird

**Rueckgabe:** Eine `ConsoleSpinner`-Instanz, die Sie manuell steuern koennen

**Beispiel mit manueller Steuerung:**
```dart
@override
Future<void> handle(CommandResult result) async {
  // Spinner-Instanz erstellen
  final spinner = createSpinner('Deploying to production');
  spinner.start();

  try {
    // Erste Aufgabe
    await runProcess('flutter clean', silent: true);
    spinner.update('Building release version');

    // Zweite Aufgabe
    await runProcess('flutter build web --release', silent: true);
    spinner.update('Uploading to server');

    // Dritte Aufgabe
    await runProcess('./deploy.sh', silent: true);

    // Erfolgreich abschliessen
    spinner.stop(completionMessage: 'Deployment completed successfully', success: true);
  } catch (e) {
    // Fehler behandeln
    spinner.stop(completionMessage: 'Deployment failed: $e', success: false);
    rethrow;
  }
}
```

<div id="spinner-examples"></div>

## Beispiele

### Einfache Aufgabe mit Spinner

```dart
@override
Future<void> handle(CommandResult result) async {
  await withSpinner(
    task: () async {
      // Abhaengigkeiten installieren
      await runProcess('flutter pub get', silent: true);
      return true;
    },
    message: 'Installing dependencies',
    successMessage: 'Dependencies installed successfully',
  );
}
```

### Mehrere aufeinanderfolgende Operationen

```dart
@override
Future<void> handle(CommandResult result) async {
  // Erste Operation mit Spinner
  await withSpinner(
    task: () => runProcess('flutter clean', silent: true),
    message: 'Cleaning project',
  );

  // Zweite Operation mit Spinner
  await withSpinner(
    task: () => runProcess('flutter pub get', silent: true),
    message: 'Updating dependencies',
  );

  // Dritte Operation mit Spinner
  final buildSuccess = await withSpinner(
    task: () async {
      await runProcess('flutter build apk --release', silent: true);
      return true;
    },
    message: 'Building release APK',
    successMessage: 'Release APK built successfully',
  );

  if (buildSuccess) {
    success('Build process completed');
  }
}
```

### Komplexer Workflow mit manueller Steuerung

```dart
@override
Future<void> handle(CommandResult result) async {
  final spinner = createSpinner('Starting deployment process');
  spinner.start();

  try {
    // Mehrere Schritte mit Statusaktualisierungen ausfuehren
    spinner.update('Step 1: Cleaning project');
    await runProcess('flutter clean', silent: true);

    spinner.update('Step 2: Fetching dependencies');
    await runProcess('flutter pub get', silent: true);

    spinner.update('Step 3: Building release');
    await runProcess('flutter build web --release', silent: true);

    // Prozess abschliessen
    spinner.stop(completionMessage: 'Deployment completed successfully', success: true);

  } catch (e) {
    spinner.stop(completionMessage: 'Deployment failed: $e', success: false);
  }
}
```

Die Verwendung von Spinnern in Ihren benutzerdefinierten Commands bietet klares visuelles Feedback fuer Benutzer waehrend lang laufender Operationen und schafft ein professionelleres Befehlszeilenerlebnis.

<div id="custom-command-helper-get-string"></div>

### Einen String-Wert aus Optionen abrufen

```dart
String getString(String name, {String defaultValue = ''})
```

**Parameter:**

- `name`: Der Name der abzurufenden Option
- `defaultValue`: Optionaler Standardwert, wenn die Option nicht angegeben wurde

**Rueckgabe:** Der Wert der Option als String

**Beispiel:**
```dart
@override
CommandBuilder builder(CommandBuilder command) {
  command.addOption("name", defaultValue: "Anthony");
  return command;
}

Future<void> handle(CommandResult result) async {
  final name = result.getString('name');
  info('Hello, $name!');
}
```

<div id="custom-command-helper-get-bool"></div>

### Einen booleschen Wert aus Optionen abrufen

```dart
bool getBool(String name, {bool defaultValue = false})
```

**Parameter:**
- `name`: Der Name der abzurufenden Option
- `defaultValue`: Optionaler Standardwert, wenn die Option nicht angegeben wurde

**Rueckgabe:** Der Wert der Option als Boolean


**Beispiel:**
```dart
@override
CommandBuilder builder(CommandBuilder command) {
  command.addFlag("verbose", defaultValue: false);
  return command;
}

Future<void> handle(CommandResult result) async {
  final verbose = result.getBool('verbose');
  if (verbose) {
    info('Verbose mode enabled');
  } else {
    info('Verbose mode disabled');
  }
}
```

<div id="custom-command-helper-get-int"></div>

### Einen Integer-Wert aus Optionen abrufen

```dart
int getInt(String name, {int defaultValue = 0})
```

**Parameter:**
- `name`: Der Name der abzurufenden Option
- `defaultValue`: Optionaler Standardwert, wenn die Option nicht angegeben wurde

**Rueckgabe:** Der Wert der Option als Integer

**Beispiel:**
```dart
@override
CommandBuilder builder(CommandBuilder command) {
  command.addOption("count", defaultValue: 5);
  return command;
}

Future<void> handle(CommandResult result) async {
  final count = result.getInt('count');
  info('Count is set to $count');
}
```

<div id="custom-command-helper-sleep"></div>

### Fuer eine bestimmte Dauer pausieren

```dart
void sleep(int seconds)
```

**Parameter:**
- `seconds`: Die Anzahl der Sekunden zum Pausieren

**Rueckgabe:** Keine

**Beispiel:**
```dart
@override
Future<void> handle(CommandResult result) async {
  info('Sleeping for 5 seconds...');
  await sleep(5);
  info('Awake now!');
}
```

<div id="output-formatting"></div>

## Ausgabeformatierung

Ueber die grundlegenden `info`-, `error`-, `success`- und `warning`-Methoden hinaus bietet `NyCustomCommand` zusaetzliche Ausgabe-Helfer:

```dart
@override
Future<void> handle(CommandResult result) async {
  // Einfachen Text ausgeben (keine Farbe)
  line('Processing your request...');

  // Leerzeilen ausgeben
  newLine();       // eine Leerzeile
  newLine(3);      // drei Leerzeilen

  // Gedaempften Kommentar ausgeben (grauer Text)
  comment('This is a background note');

  // Auffaelligen Alarmkasten ausgeben
  alert('Important: Please read carefully');

  // Ask ist ein Alias fuer prompt
  final name = ask('What is your name?');

  // Versteckte Eingabe fuer sensible Daten (z.B. Passwoerter, API-Schluessels)
  final apiKey = promptSecret('Enter your API key:');

  // Command mit Fehlermeldung und Exit-Code abbrechen
  if (name.isEmpty) {
    abort('Name is required');  // beendet mit Code 1
  }
}
```

| Methode | Beschreibung |
|---------|-------------|
| `line(String message)` | Einfachen Text ohne Farbe ausgeben |
| `newLine([int count = 1])` | Leerzeilen ausgeben |
| `comment(String message)` | Gedaempften/grauen Text ausgeben |
| `alert(String message)` | Einen auffaelligen Alarmkasten ausgeben |
| `ask(String question, {String defaultValue})` | Alias fuer `prompt` |
| `promptSecret(String question)` | Versteckte Eingabe fuer sensible Daten |
| `abort([String? message, int exitCode = 1])` | Den Command mit einem Fehler beenden |

<div id="file-system-helpers"></div>

## Dateisystem-Helfer

`NyCustomCommand` enthaelt eingebaute Dateisystem-Helfer, sodass Sie `dart:io` nicht manuell fuer gaengige Operationen importieren muessen.

### Dateien lesen und schreiben

```dart
@override
Future<void> handle(CommandResult result) async {
  // Pruefen, ob eine Datei existiert
  if (fileExists('lib/config/app.dart')) {
    info('Config file found');
  }

  // Pruefen, ob ein Verzeichnis existiert
  if (directoryExists('lib/app/models')) {
    info('Models directory found');
  }

  // Datei lesen (asynchron)
  String content = await readFile('pubspec.yaml');

  // Datei lesen (synchron)
  String contentSync = readFileSync('pubspec.yaml');

  // In eine Datei schreiben (asynchron)
  await writeFile('lib/generated/output.dart', 'class Output {}');

  // In eine Datei schreiben (synchron)
  writeFileSync('lib/generated/output.dart', 'class Output {}');

  // Inhalt an eine Datei anhaengen
  await appendFile('log.txt', 'New log entry\n');

  // Sicherstellen, dass ein Verzeichnis existiert (erstellt es, falls nicht vorhanden)
  await ensureDirectory('lib/generated');

  // Eine Datei loeschen
  await deleteFile('lib/generated/output.dart');

  // Eine Datei kopieren
  await copyFile('lib/config/app.dart', 'lib/config/app.bak.dart');
}
```

| Methode | Beschreibung |
|---------|-------------|
| `fileExists(String path)` | Gibt `true` zurueck, wenn die Datei existiert |
| `directoryExists(String path)` | Gibt `true` zurueck, wenn das Verzeichnis existiert |
| `readFile(String path)` | Datei als String lesen (async) |
| `readFileSync(String path)` | Datei als String lesen (sync) |
| `writeFile(String path, String content)` | Inhalt in Datei schreiben (async) |
| `writeFileSync(String path, String content)` | Inhalt in Datei schreiben (sync) |
| `appendFile(String path, String content)` | Inhalt an Datei anhaengen |
| `ensureDirectory(String path)` | Verzeichnis erstellen, falls nicht vorhanden |
| `deleteFile(String path)` | Eine Datei loeschen |
| `copyFile(String source, String destination)` | Eine Datei kopieren |

<div id="json-yaml-helpers"></div>

## JSON- und YAML-Helfer

JSON- und YAML-Dateien mit eingebauten Helfern lesen und schreiben.

```dart
@override
Future<void> handle(CommandResult result) async {
  // JSON-Datei als Map lesen
  Map<String, dynamic> config = await readJson('config.json');

  // JSON-Datei als Liste lesen
  List<dynamic> items = await readJsonArray('lib/app/commands/commands.json');

  // Daten in eine JSON-Datei schreiben (standardmaessig als pretty-print)
  await writeJson('output.json', {'name': 'MyApp', 'version': '1.0.0'});

  // Kompaktes JSON schreiben
  await writeJson('output.json', data, pretty: false);

  // Ein Element an eine JSON-Array-Datei anhaengen
  // Falls die Datei [{"name": "a"}] enthaelt, wird dies dem Array hinzugefuegt
  await appendToJsonArray(
    'lib/app/commands/commands.json',
    {'name': 'my_command', 'category': 'app', 'script': 'my_command.dart'},
    uniqueKey: 'name',  // prevents duplicates by this key
  );

  // YAML-Datei als Map lesen
  Map<String, dynamic> pubspec = await readYaml('pubspec.yaml');
  info('Project: ${pubspec['name']}');
}
```

| Methode | Beschreibung |
|---------|-------------|
| `readJson(String path)` | JSON-Datei als `Map<String, dynamic>` lesen |
| `readJsonArray(String path)` | JSON-Datei als `List<dynamic>` lesen |
| `writeJson(String path, dynamic data, {bool pretty = true})` | Daten als JSON schreiben |
| `appendToJsonArray(String path, Map item, {String? uniqueKey})` | An eine JSON-Array-Datei anhaengen |
| `readYaml(String path)` | YAML-Datei als `Map<String, dynamic>` lesen |

<div id="case-conversion-helpers"></div>

## Gross-/Kleinschreibungs-Konvertierungshelfer

Strings zwischen Namenskonventionen konvertieren, ohne das `recase`-Paket importieren zu muessen.

```dart
@override
Future<void> handle(CommandResult result) async {
  String input = 'user profile page';

  info(snakeCase(input));    // user_profile_page
  info(camelCase(input));    // userProfilePage
  info(pascalCase(input));   // UserProfilePage
  info(titleCase(input));    // User Profile Page
  info(kebabCase(input));    // user-profile-page
  info(constantCase(input)); // USER_PROFILE_PAGE
}
```

| Methode | Ausgabeformat | Beispiel |
|---------|--------------|---------|
| `snakeCase(String input)` | `snake_case` | `user_profile` |
| `camelCase(String input)` | `camelCase` | `userProfile` |
| `pascalCase(String input)` | `PascalCase` | `UserProfile` |
| `titleCase(String input)` | `Title Case` | `User Profile` |
| `kebabCase(String input)` | `kebab-case` | `user-profile` |
| `constantCase(String input)` | `CONSTANT_CASE` | `USER_PROFILE` |

<div id="project-path-helpers"></div>

## Projektpfad-Helfer

Getter fuer Standard-Nylo-Projektverzeichnisse. Diese geben Pfade relativ zum Projektstamm zurueck.

```dart
@override
Future<void> handle(CommandResult result) async {
  info(modelsPath);       // lib/app/models
  info(controllersPath);  // lib/app/controllers
  info(widgetsPath);      // lib/resources/widgets
  info(pagesPath);        // lib/resources/pages
  info(commandsPath);     // lib/app/commands
  info(configPath);       // lib/config
  info(providersPath);    // lib/app/providers
  info(eventsPath);       // lib/app/events
  info(networkingPath);   // lib/app/networking
  info(themesPath);       // lib/resources/themes

  // Benutzerdefinierten Pfad relativ zum Projektstamm erstellen
  String customPath = projectPath('lib/app/services/auth_service.dart');
}
```

| Eigenschaft | Pfad |
|------------|------|
| `modelsPath` | `lib/app/models` |
| `controllersPath` | `lib/app/controllers` |
| `widgetsPath` | `lib/resources/widgets` |
| `pagesPath` | `lib/resources/pages` |
| `commandsPath` | `lib/app/commands` |
| `configPath` | `lib/config` |
| `providersPath` | `lib/app/providers` |
| `eventsPath` | `lib/app/events` |
| `networkingPath` | `lib/app/networking` |
| `themesPath` | `lib/resources/themes` |
| `projectPath(String relativePath)` | Einen relativen Pfad innerhalb des Projekts aufloesen |

<div id="platform-helpers"></div>

## Plattform-Helfer

Plattform pruefen und auf Umgebungsvariablen zugreifen.

```dart
@override
Future<void> handle(CommandResult result) async {
  // Plattform-Pruefungen
  if (isWindows) {
    info('Running on Windows');
  } else if (isMacOS) {
    info('Running on macOS');
  } else if (isLinux) {
    info('Running on Linux');
  }

  // Aktuelles Arbeitsverzeichnis
  info('Working in: $workingDirectory');

  // System-Umgebungsvariablen lesen
  String home = env('HOME', '/default/path');
}
```

| Eigenschaft / Methode | Beschreibung |
|----------------------|-------------|
| `isWindows` | `true`, wenn auf Windows ausgefuehrt |
| `isMacOS` | `true`, wenn auf macOS ausgefuehrt |
| `isLinux` | `true`, wenn auf Linux ausgefuehrt |
| `workingDirectory` | Aktueller Arbeitsverzeichnispfad |
| `env(String key, [String defaultValue = ''])` | System-Umgebungsvariable lesen |

<div id="dart-flutter-commands"></div>

## Dart- und Flutter-Befehle

Gaengige Dart- und Flutter-CLI-Befehle als Hilfsmethoden ausfuehren. Jede gibt den Prozess-Exit-Code zurueck.

```dart
@override
Future<void> handle(CommandResult result) async {
  // Eine Dart-Datei oder ein Verzeichnis formatieren
  await dartFormat('lib/app/models/user.dart');

  // dart analyze ausfuehren
  int analyzeResult = await dartAnalyze('lib/');

  // flutter pub get ausfuehren
  await flutterPubGet();

  // flutter clean ausfuehren
  await flutterClean();

  // Fuer ein Ziel mit zusaetzlichen Argumenten bauen
  await flutterBuild('apk', args: ['--release', '--split-per-abi']);
  await flutterBuild('web', args: ['--release']);

  // flutter test ausfuehren
  await flutterTest();
  await flutterTest('test/unit/');  // bestimmtes Verzeichnis
}
```

| Methode | Beschreibung |
|---------|-------------|
| `dartFormat(String path)` | `dart format` auf eine Datei oder ein Verzeichnis ausfuehren |
| `dartAnalyze([String? path])` | `dart analyze` ausfuehren |
| `flutterPubGet()` | `flutter pub get` ausfuehren |
| `flutterClean()` | `flutter clean` ausfuehren |
| `flutterBuild(String target, {List<String> args})` | `flutter build <target>` ausfuehren |
| `flutterTest([String? path])` | `flutter test` ausfuehren |

<div id="dart-file-manipulation"></div>

## Dart-Dateimanipulation

Helfer zum programmatischen Bearbeiten von Dart-Dateien, nuetzlich beim Erstellen von Scaffolding-Tools.

```dart
@override
Future<void> handle(CommandResult result) async {
  // Import-Anweisung zu einer Dart-Datei hinzufuegen (vermeidet Duplikate)
  await addImport(
    'lib/bootstrap/providers.dart',
    "import '/app/providers/firebase_provider.dart';",
  );

  // Code vor der letzten schliessenden Klammer in einer Datei einfuegen
  // Nuetzlich zum Hinzufuegen von Eintraegen in Registrierungs-Maps
  await insertBeforeClosingBrace(
    'lib/bootstrap/providers.dart',
    '  FirebaseProvider(),',
  );

  // Pruefen, ob eine Datei einen bestimmten String enthaelt
  bool hasImport = await fileContains(
    'lib/bootstrap/providers.dart',
    'firebase_provider',
  );

  // Pruefen, ob eine Datei einem Regex-Pattern entspricht
  bool hasClass = await fileContainsPattern(
    'lib/app/models/user.dart',
    RegExp(r'class User'),
  );
}
```

| Methode | Beschreibung |
|---------|-------------|
| `addImport(String filePath, String importStatement)` | Import zu Dart-Datei hinzufuegen (ueberspringt, wenn bereits vorhanden) |
| `insertBeforeClosingBrace(String filePath, String code)` | Code vor letzter `}` in Datei einfuegen |
| `fileContains(String filePath, String identifier)` | Pruefen, ob Datei einen String enthaelt |
| `fileContainsPattern(String filePath, Pattern pattern)` | Pruefen, ob Datei einem Pattern entspricht |

<div id="directory-helpers"></div>

## Verzeichnis-Helfer

Helfer fuer die Arbeit mit Verzeichnissen und das Finden von Dateien.

```dart
@override
Future<void> handle(CommandResult result) async {
  // Verzeichnisinhalte auflisten
  var entities = listDirectory('lib/app/models');
  for (var entity in entities) {
    info(entity.path);
  }

  // Rekursiv auflisten
  var allEntities = listDirectory('lib/', recursive: true);

  // Dateien nach Kriterien suchen
  List<File> dartFiles = findFiles(
    'lib/app/models',
    extension: '.dart',
    recursive: true,
  );

  // Dateien nach Namensmuster suchen
  List<File> testFiles = findFiles(
    'test/',
    namePattern: RegExp(r'_test\.dart$'),
  );

  // Verzeichnis rekursiv loeschen
  await deleteDirectory('build/');

  // Verzeichnis kopieren (rekursiv)
  await copyDirectory('lib/templates', 'lib/generated');
}
```

| Methode | Beschreibung |
|---------|-------------|
| `listDirectory(String path, {bool recursive = false})` | Verzeichnisinhalte auflisten |
| `findFiles(String directory, {String? extension, Pattern? namePattern, bool recursive = true})` | Dateien nach Kriterien finden |
| `deleteDirectory(String path)` | Verzeichnis rekursiv loeschen |
| `copyDirectory(String source, String destination)` | Verzeichnis rekursiv kopieren |

<div id="validation-helpers"></div>

## Validierungs-Helfer

Helfer zum Validieren und Bereinigen von Benutzereingaben fuer die Code-Generierung.

```dart
@override
Future<void> handle(CommandResult result) async {
  // Dart-Bezeichner validieren
  if (!isValidDartIdentifier('MyClass')) {
    error('Invalid Dart identifier');
  }

  // Nicht-leeres erstes Argument erfordern
  String name = requireArgument(result, message: 'Please provide a name');

  // Klassenname bereinigen (PascalCase, Suffixe entfernen)
  String className = cleanClassName('user_model', removeSuffixes: ['_model']);
  // Gibt zurueck: 'User'

  // Dateiname bereinigen (snake_case mit Erweiterung)
  String fileName = cleanFileName('UserModel', extension: '.dart');
  // Gibt zurueck: 'user_model.dart'
}
```

| Methode | Beschreibung |
|---------|-------------|
| `isValidDartIdentifier(String name)` | Einen Dart-Bezeichnernamen validieren |
| `requireArgument(CommandResult result, {String? message})` | Nicht-leeres erstes Argument erfordern oder abbrechen |
| `cleanClassName(String name, {List<String> removeSuffixes})` | Einen Klassennamen bereinigen und in PascalCase konvertieren |
| `cleanFileName(String name, {String extension = '.dart'})` | Einen Dateinamen bereinigen und in snake_case konvertieren |

<div id="file-scaffolding"></div>

## Datei-Scaffolding

Eine oder mehrere Dateien mit Inhalt mithilfe des Scaffolding-Systems erstellen.

### Einzelne Datei

```dart
@override
Future<void> handle(CommandResult result) async {
  await scaffold(
    path: 'lib/app/services/auth_service.dart',
    content: '''
class AuthService {
  Future<bool> login(String email, String password) async {
    // TODO: implement login
    return false;
  }
}
''',
    force: false,  // don't overwrite if exists
    successMessage: 'AuthService created',
  );
}
```

### Mehrere Dateien

```dart
@override
Future<void> handle(CommandResult result) async {
  await scaffoldMany([
    ScaffoldFile(
      path: 'lib/app/models/product.dart',
      content: 'class Product {}',
      successMessage: 'Product model created',
    ),
    ScaffoldFile(
      path: 'lib/app/networking/product_api_service.dart',
      content: 'class ProductApiService {}',
      successMessage: 'Product API service created',
    ),
  ], force: false);
}
```

Die `ScaffoldFile`-Klasse akzeptiert:

| Eigenschaft | Typ | Beschreibung |
|------------|------|-------------|
| `path` | `String` | Zu erstellender Dateipfad |
| `content` | `String` | Dateiinhalt |
| `successMessage` | `String?` | Bei Erfolg angezeigte Nachricht |

<div id="task-runner"></div>

## Task-Runner

Eine Reihe benannter Aufgaben mit automatischer Statusausgabe ausfuehren.

### Grundlegender Task-Runner

```dart
@override
Future<void> handle(CommandResult result) async {
  await runTasks([
    CommandTask(
      'Clean project',
      () => runProcess('flutter clean', silent: true),
    ),
    CommandTask(
      'Fetch dependencies',
      () => runProcess('flutter pub get', silent: true),
    ),
    CommandTask(
      'Run tests',
      () => runProcess('flutter test', silent: true),
      stopOnError: true,  // stop pipeline if this fails (default)
    ),
  ]);
}
```

### Task-Runner mit Spinner

```dart
@override
Future<void> handle(CommandResult result) async {
  await runTasksWithSpinner([
    CommandTask(
      name: 'Preparing release',
      action: () async {
        await flutterClean();
        await flutterPubGet();
      },
    ),
    CommandTask(
      name: 'Building APK',
      action: () => flutterBuild('apk', args: ['--release']),
    ),
  ]);
}
```

Die `CommandTask`-Klasse akzeptiert:

| Eigenschaft | Typ | Standard | Beschreibung |
|------------|------|---------|-------------|
| `name` | `String` | erforderlich | In der Ausgabe angezeigter Aufgabenname |
| `action` | `Future<void> Function()` | erforderlich | Auszufuehrende asynchrone Funktion |
| `stopOnError` | `bool` | `true` | Ob verbleibende Aufgaben gestoppt werden sollen, wenn diese fehlschlaegt |

<div id="table-output"></div>

## Tabellenausgabe

Formatierte ASCII-Tabellen in der Konsole anzeigen.

```dart
@override
Future<void> handle(CommandResult result) async {
  table(
    ['Name', 'Version', 'Status'],
    [
      ['nylo_framework', '7.0.0', 'installed'],
      ['nylo_support', '7.0.0', 'installed'],
      ['dio', '5.4.0', 'installed'],
    ],
  );
}
```

Ausgabe:

```
┌─────────────────┬─────────┬───────────┐
│ Name            │ Version │ Status    │
├─────────────────┼─────────┼───────────┤
│ nylo_framework  │ 7.0.0   │ installed │
│ nylo_support    │ 7.0.0   │ installed │
│ dio             │ 5.4.0   │ installed │
└─────────────────┴─────────┴───────────┘
```

<div id="progress-bar"></div>

## Fortschrittsbalken

Einen Fortschrittsbalken fuer Operationen mit bekannter Elementanzahl anzeigen.

### Manueller Fortschrittsbalken

```dart
@override
Future<void> handle(CommandResult result) async {
  // Fortschrittsbalken fuer 100 Elemente erstellen
  final progress = progressBar(100, message: 'Processing files');
  progress.start();

  for (int i = 0; i < 100; i++) {
    await Future.delayed(Duration(milliseconds: 50));
    progress.tick();  // um 1 erhoehen
  }

  progress.complete('All files processed');
}
```

### Elemente mit Fortschritt verarbeiten

```dart
@override
Future<void> handle(CommandResult result) async {
  final files = findFiles('lib/', extension: '.dart');

  // Elemente mit automatischer Fortschrittsverfolgung verarbeiten
  final results = await withProgress<File, String>(
    items: files,
    process: (file, index) async {
      // jede Datei verarbeiten
      return file.path;
    },
    message: 'Analyzing Dart files',
    completionMessage: 'Analysis complete',
  );

  info('Processed ${results.length} files');
}
```

### Synchroner Fortschritt

```dart
@override
Future<void> handle(CommandResult result) async {
  final items = ['a', 'b', 'c', 'd', 'e'];

  final results = withProgressSync<String, String>(
    items: items,
    process: (item, index) {
      // synchrone Verarbeitung
      return item.toUpperCase();
    },
    message: 'Converting items',
  );

  info('Results: $results');
}
```

Die `ConsoleProgressBar`-Klasse bietet:

| Methode | Beschreibung |
|---------|-------------|
| `start()` | Den Fortschrittsbalken starten |
| `tick([int amount = 1])` | Fortschritt erhoehen |
| `update(int value)` | Fortschritt auf einen bestimmten Wert setzen |
| `updateMessage(String newMessage)` | Die angezeigte Nachricht aendern |
| `complete([String? completionMessage])` | Mit optionaler Nachricht abschliessen |
| `stop()` | Ohne Abschluss stoppen |
| `current` | Aktueller Fortschrittswert (Getter) |
| `percentage` | Fortschritt als Prozentsatz (Getter) |
