Basics

Metro CLI-Tool

Einleitung

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

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:

metro

Sie sollten eine Ausgabe aehnlich der folgenden sehen.

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

Controller erstellen

Einen neuen Controller erstellen

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

metro make:controller profile_controller

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

Controller erzwungen erstellen

Argumente:

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

metro make:controller profile_controller --force

Model erstellen

Ein neues Model erstellen

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

metro make:model product

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

Ein Model aus JSON erstellen

Argumente:

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

metro make:model product --json

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

Model erzwungen erstellen

Argumente:

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

metro make:model product --force

Seite erstellen

Eine neue Seite erstellen

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

metro make:page product_page

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

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.

metro make:page product_page -c

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.

metro make:page login_page -a

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.

metro make:page home_page -i

Seite erzwungen erstellen

Argumente:

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

metro make:page product_page --force

Stateless Widget erstellen

Ein neues Stateless Widget erstellen

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

metro make:stateless_widget product_rating_widget

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

Stateless Widget erzwungen erstellen

Argumente:

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

metro make:stateless_widget product_rating_widget --force

Stateful Widget erstellen

Ein neues Stateful Widget erstellen

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

metro make:stateful_widget product_rating_widget

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

Stateful Widget erzwungen erstellen

Argumente:

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

metro make:stateful_widget product_rating_widget --force

Journey Widget erstellen

Ein neues Journey Widget erstellen

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

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

metro make:navigation_hub onboarding

Fuegen Sie als Naechstes die neuen Journey Widgets hinzu.

metro make:journey_widget welcome,user_dob,user_photos --parent="onboarding"

Journey Widget erzwungen erstellen

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

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

API Service erstellen

Einen neuen API Service erstellen

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

metro make:api_service user_api_service

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

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.

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.

metro make:api_service user --force

Event erstellen

Ein neues Event erstellen

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

metro make:event login_event

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

Event erzwungen erstellen

Argumente:

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

metro make:event login_event --force

Provider erstellen

Einen neuen Provider erstellen

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

metro make:provider firebase_provider

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

Provider erzwungen erstellen

Argumente:

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

metro make:provider firebase_provider --force

Theme erstellen

Ein neues Theme erstellen

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

metro make:theme bright_theme

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

Theme erzwungen erstellen

Argumente:

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

metro make:theme bright_theme --force

Formulare erstellen

Ein neues Formular erstellen

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

metro make:form car_advert_form

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

Formular erzwungen erstellen

Argumente:

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

metro make:form login_form --force

Route Guard erstellen

Einen neuen Route Guard erstellen

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

metro make:route_guard premium_content

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

Route Guard erzwungen erstellen

Argumente:

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

metro make:route_guard premium_content --force

Config-Datei erstellen

Eine neue Config-Datei erstellen

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

metro make:config shopping_settings

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

Config-Datei erzwungen erstellen

Argumente:

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

metro make:config app_config --force

Command erstellen

Einen neuen Command erstellen

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

metro make:command my_command

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

Command erzwungen erstellen

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

metro make:command my_command --force

State Managed Widget erstellen

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

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.

metro make:state_managed_widget product_rating_widget --force

Navigation Hub erstellen

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

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
# Create as the initial page
metro make:navigation_hub dashboard --initial

Bottom Sheet Modal erstellen

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

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.

metro make:bottom_sheet_modal payment_options --force

Button erstellen

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

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.

metro make:button checkout_button --force

Interceptor erstellen

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

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.

metro make:interceptor auth_interceptor --force

Env-Datei erstellen

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

metro make:env .env.staging

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

Key generieren

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

metro make:key

Argumente:

Flag / Option Kurz Beschreibung
--force -f Vorhandenen APP_KEY ueberschreiben
--file -e Ziel-.env-Datei (Standard: .env)
# Generate key and overwrite existing
metro make:key --force

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

App-Icons erstellen

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

dart run flutter_launcher_icons:main

Dies verwendet die flutter_icons-Konfiguration in Ihrer pubspec.yaml-Datei.

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.

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

Benutzerdefinierte Commands erstellen

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

metro make:command current_time

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

# 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:

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

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

/// Current Time Command
///
/// Usage:
///   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");

      // Get the current time
      final now = DateTime.now();
      final DateFormat dateFormat = DateFormat(format);

      // Format the current time
      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:

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

Benutzerdefinierte Commands ausfuehren

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

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:

metro project:install_firebase --help

Optionen zu Commands hinzufuegen

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

@override
CommandBuilder builder(CommandBuilder command) {

  // Add an option with a default value
  command.addOption(
    'environment',     // option name
    abbr: 'e',         // short form abbreviation
    help: 'Target deployment environment', // help text
    defaultValue: 'development',  // default value
    allowed: ['development', 'staging', 'production'] // allowed values
  );

  return command;
}

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

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

  // Command implementation...
}

Beispielverwendung:

metro project:deploy --environment=production
# or using abbreviation
metro project:deploy -e production

Flags zu Commands hinzufuegen

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

@override
CommandBuilder builder(CommandBuilder command) {

  command.addFlag(
    'verbose',       // flag name
    abbr: 'v',       // short form abbreviation
    help: 'Enable verbose output', // help text
    defaultValue: false  // default to off
  );

  return command;
}

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

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

  if (verbose) {
    info('Verbose mode enabled');
    // Additional logging...
  }

  // Command implementation...
}

Beispielverwendung:

metro project:deploy --verbose
# or using abbreviation
metro project:deploy -v

Hilfsmethoden

Die Basisklasse NyCustomCommand bietet mehrere Hilfsmethoden fuer gaengige Aufgaben:

Nachrichten ausgeben

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

info Eine Info-Nachricht in blauem Text ausgeben
error Eine Fehlernachricht in rotem Text ausgeben
success Eine Erfolgsnachricht in gruenem Text ausgeben
warning Eine Warnungsnachricht in gelbem Text ausgeben

Prozesse ausfuehren

Prozesse ausfuehren und ihre Ausgabe in der Konsole anzeigen:

addPackage Ein Paket zur pubspec.yaml hinzufuegen
addPackages Mehrere Pakete zur pubspec.yaml hinzufuegen
runProcess Einen externen Prozess ausfuehren und Ausgabe in der Konsole anzeigen
prompt Benutzereingabe als Text erfassen
confirm Eine Ja/Nein-Frage stellen und ein boolesches Ergebnis zurueckgeben
select Eine Liste von Optionen praesentieren und den Benutzer eine auswaehlen lassen
multiSelect Den Benutzer mehrere Optionen aus einer Liste auswaehlen lassen

Netzwerkanfragen

Netzwerkanfragen ueber die Konsole stellen:

api Einen API-Aufruf mit dem Nylo API-Client durchfuehren

Ladeanzeige

Eine Ladeanzeige waehrend der Ausfuehrung einer Funktion anzeigen:

withSpinner Eine Ladeanzeige waehrend der Ausfuehrung einer Funktion anzeigen
createSpinner Eine Spinner-Instanz fuer manuelle Steuerung erstellen

Helfer fuer benutzerdefinierte Commands

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

getString Einen String-Wert aus den Command-Argumenten abrufen
getBool Einen booleschen Wert aus den Command-Argumenten abrufen
getInt Einen Integer-Wert aus den Command-Argumenten abrufen
sleep Die Ausfuehrung fuer eine bestimmte Dauer pausieren

Externe Prozesse ausfuehren

// Run a process with output displayed in the console
await runProcess('flutter build web --release');

// Run a process silently
await runProcess('flutter pub get', silent: true);

// Run a process in a specific directory
await runProcess('git pull', workingDirectory: './my-project');

Paketverwaltung

// Add a package to pubspec.yaml
addPackage('firebase_core', version: '^2.4.0');

// Add a dev package to pubspec.yaml
addPackage('build_runner', dev: true);

// Add multiple packages at once
addPackages(['firebase_auth', 'firebase_storage', 'quickalert']);

Ausgabeformatierung

// Print status messages with color coding
info('Processing files...');    // Blue text
error('Operation failed');      // Red text
success('Deployment complete'); // Green text
warning('Outdated package');    // Yellow text

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.

Texteingabe

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:

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

Bestaetigung

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:

if (confirm('Would you like to continue?', defaultValue: true)) {
  // User confirmed or pressed Enter (accepting the default)
  await runProcess('flutter pub get');
} else {
  // User declined
  info('Operation canceled');
}

Einzelauswahl

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:

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

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

Mehrfachauswahl

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:

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');
}

API-Hilfsmethode

Die api-Hilfsmethode vereinfacht Netzwerkanfragen aus Ihren benutzerdefinierten Commands.

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

Grundlegende Verwendungsbeispiele

GET-Anfrage

// Fetch data
final userData = await api((request) =>
  request.get('https://api.example.com/users/1')
);

POST-Anfrage

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

PUT-Anfrage

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

DELETE-Anfrage

// Delete a resource
final deleteResult = await api((request) => request.delete('https://api.example.com/items/42'));

PATCH-Anfrage

// Partially update a resource
final patchResult = await api((request) => request.patch(
    'https://api.example.com/items/42',
    data: {'price': 24.99}
  )
);

Mit Query-Parametern

// Add query parameters
final searchResults = await api((request) => request.get(
    'https://api.example.com/search',
    queryParameters: {'q': 'keyword', 'limit': 10}
  )
);

Mit Spinner

// Using with spinner for better UI
final data = await withSpinner(
  task: () async {
    final data = await api((request) => request.get('https://api.example.com/config'));
    // Process the data
  },
  message: 'Loading configuration',
);

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

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:

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:

@override
Future<void> handle(CommandResult result) async {
  // Run a task with a spinner
  final projectFiles = await withSpinner(
    task: () async {
      // Long-running task (e.g., analyzing project files)
      await sleep(2);
      return ['pubspec.yaml', 'lib/main.dart', 'README.md'];
    },
    message: 'Analyzing project structure',
    successMessage: 'Project analysis complete',
    errorMessage: 'Failed to analyze project',
  );

  // Continue with the results
  info('Found ${projectFiles.length} key files');
}

Manuelle Spinner-Steuerung

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

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:

@override
Future<void> handle(CommandResult result) async {
  // Create a spinner instance
  final spinner = createSpinner('Deploying to production');
  spinner.start();

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

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

    // Third task
    await runProcess('./deploy.sh', silent: true);

    // Complete successfully
    spinner.stop(completionMessage: 'Deployment completed successfully', success: true);
  } catch (e) {
    // Handle failure
    spinner.stop(completionMessage: 'Deployment failed: $e', success: false);
    rethrow;
  }
}

Beispiele

Einfache Aufgabe mit Spinner

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

Mehrere aufeinanderfolgende Operationen

@override
Future<void> handle(CommandResult result) async {
  // First operation with spinner
  await withSpinner(
    task: () => runProcess('flutter clean', silent: true),
    message: 'Cleaning project',
  );

  // Second operation with spinner
  await withSpinner(
    task: () => runProcess('flutter pub get', silent: true),
    message: 'Updating dependencies',
  );

  // Third operation with 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

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

  try {
    // Run multiple steps with status updates
    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);

    // Complete the process
    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.

Einen String-Wert aus Optionen abrufen

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:

@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!');
}

Einen booleschen Wert aus Optionen abrufen

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:

@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');
  }
}

Einen Integer-Wert aus Optionen abrufen

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:

@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');
}

Fuer eine bestimmte Dauer pausieren

void sleep(int seconds)

Parameter:

  • seconds: Die Anzahl der Sekunden zum Pausieren

Rueckgabe: Keine

Beispiel:

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

Ausgabeformatierung

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

@override
Future<void> handle(CommandResult result) async {
  // Print plain text (no color)
  line('Processing your request...');

  // Print blank lines
  newLine();       // one blank line
  newLine(3);      // three blank lines

  // Print a muted comment (gray text)
  comment('This is a background note');

  // Print a prominent alert box
  alert('Important: Please read carefully');

  // Ask is an alias for prompt
  final name = ask('What is your name?');

  // Hidden input for sensitive data (e.g., passwords, API keys)
  final apiKey = promptSecret('Enter your API key:');

  // Abort the command with an error message and exit code
  if (name.isEmpty) {
    abort('Name is required');  // exits with 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

Dateisystem-Helfer

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

Dateien lesen und schreiben

@override
Future<void> handle(CommandResult result) async {
  // Check if a file exists
  if (fileExists('lib/config/app.dart')) {
    info('Config file found');
  }

  // Check if a directory exists
  if (directoryExists('lib/app/models')) {
    info('Models directory found');
  }

  // Read a file (async)
  String content = await readFile('pubspec.yaml');

  // Read a file (sync)
  String contentSync = readFileSync('pubspec.yaml');

  // Write to a file (async)
  await writeFile('lib/generated/output.dart', 'class Output {}');

  // Write to a file (sync)
  writeFileSync('lib/generated/output.dart', 'class Output {}');

  // Append content to a file
  await appendFile('log.txt', 'New log entry\n');

  // Ensure a directory exists (creates it if missing)
  await ensureDirectory('lib/generated');

  // Delete a file
  await deleteFile('lib/generated/output.dart');

  // Copy a file
  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

JSON- und YAML-Helfer

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

@override
Future<void> handle(CommandResult result) async {
  // Read a JSON file as a Map
  Map<String, dynamic> config = await readJson('config.json');

  // Read a JSON file as a List
  List<dynamic> items = await readJsonArray('lib/app/commands/commands.json');

  // Write data to a JSON file (pretty printed by default)
  await writeJson('output.json', {'name': 'MyApp', 'version': '1.0.0'});

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

  // Append an item to a JSON array file
  // If the file contains [{"name": "a"}], this adds to that array
  await appendToJsonArray(
    'lib/app/commands/commands.json',
    {'name': 'my_command', 'category': 'app', 'script': 'my_command.dart'},
    uniqueKey: 'name',  // prevents duplicates by this key
  );

  // Read a YAML file as a Map
  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

Gross-/Kleinschreibungs-Konvertierungshelfer

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

@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

Projektpfad-Helfer

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

@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

  // Build a custom path relative to the project root
  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

Plattform-Helfer

Plattform pruefen und auf Umgebungsvariablen zugreifen.

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

  // Current working directory
  info('Working in: $workingDirectory');

  // Read system environment variables
  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

Dart- und Flutter-Befehle

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

@override
Future<void> handle(CommandResult result) async {
  // Format a Dart file or directory
  await dartFormat('lib/app/models/user.dart');

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

  // Run flutter pub get
  await flutterPubGet();

  // Run flutter clean
  await flutterClean();

  // Build for a target with additional args
  await flutterBuild('apk', args: ['--release', '--split-per-abi']);
  await flutterBuild('web', args: ['--release']);

  // Run flutter test
  await flutterTest();
  await flutterTest('test/unit/');  // specific directory
}
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

Dart-Dateimanipulation

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

@override
Future<void> handle(CommandResult result) async {
  // Add an import statement to a Dart file (avoids duplicates)
  await addImport(
    'lib/bootstrap/providers.dart',
    "import '/app/providers/firebase_provider.dart';",
  );

  // Insert code before the last closing brace in a file
  // Useful for adding entries to registration maps
  await insertBeforeClosingBrace(
    'lib/bootstrap/providers.dart',
    '  FirebaseProvider(),',
  );

  // Check if a file contains a specific string
  bool hasImport = await fileContains(
    'lib/bootstrap/providers.dart',
    'firebase_provider',
  );

  // Check if a file matches a regex pattern
  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

Verzeichnis-Helfer

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

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

  // List recursively
  var allEntities = listDirectory('lib/', recursive: true);

  // Find files matching criteria
  List<File> dartFiles = findFiles(
    'lib/app/models',
    extension: '.dart',
    recursive: true,
  );

  // Find files by name pattern
  List<File> testFiles = findFiles(
    'test/',
    namePattern: RegExp(r'_test\.dart$'),
  );

  // Delete a directory recursively
  await deleteDirectory('build/');

  // Copy a directory (recursive)
  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

Validierungs-Helfer

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

@override
Future<void> handle(CommandResult result) async {
  // Validate a Dart identifier
  if (!isValidDartIdentifier('MyClass')) {
    error('Invalid Dart identifier');
  }

  // Require a non-empty first argument
  String name = requireArgument(result, message: 'Please provide a name');

  // Clean a class name (PascalCase, remove suffixes)
  String className = cleanClassName('user_model', removeSuffixes: ['_model']);
  // Returns: 'User'

  // Clean a file name (snake_case with extension)
  String fileName = cleanFileName('UserModel', extension: '.dart');
  // Returns: '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

Datei-Scaffolding

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

Einzelne Datei

@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

@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

Task-Runner

Eine Reihe benannter Aufgaben mit automatischer Statusausgabe ausfuehren.

Grundlegender Task-Runner

@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

@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

Tabellenausgabe

Formatierte ASCII-Tabellen in der Konsole anzeigen.

@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 │
└─────────────────┴─────────┴───────────┘

Fortschrittsbalken

Einen Fortschrittsbalken fuer Operationen mit bekannter Elementanzahl anzeigen.

Manueller Fortschrittsbalken

@override
Future<void> handle(CommandResult result) async {
  // Create a progress bar for 100 items
  final progress = progressBar(100, message: 'Processing files');
  progress.start();

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

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

Elemente mit Fortschritt verarbeiten

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

  // Process items with automatic progress tracking
  final results = await withProgress<File, String>(
    items: files,
    process: (file, index) async {
      // process each file
      return file.path;
    },
    message: 'Analyzing Dart files',
    completionMessage: 'Analysis complete',
  );

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

Synchroner Fortschritt

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

  final results = withProgressSync<String, String>(
    items: items,
    process: (item, index) {
      // synchronous processing
      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)